From e31b00dc8cf8ee3d3771c82c1770fab5674cfd2e Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Thu, 27 Feb 2020 00:10:09 -0800 Subject: [PATCH 001/121] First pass on reproducible matches and parallel tournaments with random seeding --- axelrod/__init__.py | 2 +- axelrod/match.py | 35 ++++- axelrod/match_generator.py | 8 +- axelrod/moran.py | 69 +++++----- axelrod/player.py | 29 ++-- axelrod/random_.py | 155 ++++++++++++++-------- axelrod/strategies/adaptor.py | 3 +- axelrod/strategies/averagecopier.py | 7 +- axelrod/strategies/axelrod_first.py | 22 ++- axelrod/strategies/axelrod_second.py | 30 ++--- axelrod/strategies/better_and_better.py | 3 +- axelrod/strategies/bush_mosteller.py | 7 +- axelrod/strategies/calculator.py | 6 +- axelrod/strategies/gambler.py | 10 +- axelrod/strategies/geller.py | 6 +- axelrod/strategies/hmm.py | 32 +++-- axelrod/strategies/inverse.py | 6 +- axelrod/strategies/memoryone.py | 5 +- axelrod/strategies/memorytwo.py | 5 +- axelrod/strategies/meta.py | 20 +-- axelrod/strategies/mutual.py | 7 +- axelrod/strategies/negation.py | 3 +- axelrod/strategies/prober.py | 6 +- axelrod/strategies/qlearner.py | 12 +- axelrod/strategies/rand.py | 3 +- axelrod/strategies/selfsteem.py | 3 +- axelrod/strategies/stalker.py | 3 +- axelrod/strategies/titfortat.py | 10 +- axelrod/strategies/worse_and_worse.py | 9 +- axelrod/strategy_transformers.py | 14 +- axelrod/tests/strategies/test_adaptive.py | 13 +- axelrod/tests/strategies/test_adaptor.py | 5 +- axelrod/tests/strategies/test_meta.py | 7 +- axelrod/tests/strategies/test_player.py | 83 ++++++------ axelrod/tests/unit/test_match.py | 28 +++- axelrod/tests/unit/test_random_.py | 75 ++++++++++- axelrod/tournament.py | 3 +- 37 files changed, 444 insertions(+), 300 deletions(-) diff --git a/axelrod/__init__.py b/axelrod/__init__.py index bdc39f202..8c135be92 100644 --- a/axelrod/__init__.py +++ b/axelrod/__init__.py @@ -5,7 +5,7 @@ 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.random_ import Pdf, RandomGenerator, BulkRandomGenerator from axelrod.plot import Plot from axelrod.game import DefaultGame, Game from axelrod.history import History, LimitedHistory diff --git a/axelrod/match.py b/axelrod/match.py index e9dd29442..3ba6e98fa 100644 --- a/axelrod/match.py +++ b/axelrod/match.py @@ -1,4 +1,3 @@ -import random from math import ceil, log import axelrod.interaction_utils as iu @@ -6,6 +5,7 @@ from axelrod.action import Action from axelrod import Classifiers from axelrod.game import Game +from axelrod.random_ import RandomGenerator from .deterministic_cache import DeterministicCache C, D = Action.C, Action.D @@ -30,6 +30,7 @@ def __init__( noise=0, match_attributes=None, reset=True, + seed=None ): """ Parameters @@ -52,6 +53,8 @@ def __init__( but these can be overridden if desired. reset : bool Whether to reset players or not + seed : int + Random seed for reproducibility """ defaults = { @@ -65,6 +68,9 @@ def __init__( self.result = [] self.noise = noise + self.seed = seed + self._random = RandomGenerator(seed=self.seed) + if game is None: self.game = Game() else: @@ -129,6 +135,19 @@ def _cached_enough_turns(self, cache_key, turns): return False return len(self._cache[cache_key]) >= turns + def simultaneous_play(self, player, coplayer, noise=0): + """This pits two players against each other.""" + s1, s2 = player.strategy(coplayer), coplayer.strategy(player) + if noise: + # Note this uses the Match classes random generator, not either + # player's random generator. A player shouldn't be able to + # predict the outcome of this noise flip. + s1 = self.random_flip(s1, noise) + s2 = self.random_flip(s2, noise) + player.update_history(s1, s2) + coplayer.update_history(s2, s1) + return s1, s2 + def play(self): """ The resulting list of actions from a match between two players. @@ -147,7 +166,9 @@ def play(self): i.e. One entry per turn containing a pair of actions. """ - turns = min(sample_length(self.prob_end), self.turns) + self._random = RandomGenerator(seed=self.seed) + r = self._random.random() + turns = min(sample_length(self.prob_end, r), self.turns) cache_key = (self.players[0], self.players[1]) if self._stochastic or not self._cached_enough_turns(cache_key, turns): @@ -155,9 +176,12 @@ def play(self): if self.reset: p.reset() p.set_match_attributes(**self.match_attributes) + # Generate a random seed for each player + p.set_seed(self._random.randint(0, 100000000)) result = [] for _ in range(turns): - plays = self.players[0].play(self.players[1], self.noise) + plays = self.simultaneous_play( + self.players[0], self.players[1], self.noise) result.append(plays) if self._cache_update_required: @@ -216,7 +240,7 @@ def __len__(self): return self.turns -def sample_length(prob_end): +def sample_length(prob_end, random_value): """ Sample length of a game. @@ -249,5 +273,4 @@ def sample_length(prob_end): return float("inf") if prob_end == 1: return 1 - x = random.random() - return int(ceil(log(1 - x) / log(1 - prob_end))) + return int(ceil(log(1 - random_value) / log(1 - prob_end))) diff --git a/axelrod/match_generator.py b/axelrod/match_generator.py index d74c143fd..dd0a49fb1 100644 --- a/axelrod/match_generator.py +++ b/axelrod/match_generator.py @@ -1,3 +1,6 @@ +from axelrod.random_ import BulkRandomGenerator + + class MatchGenerator(object): def __init__( self, @@ -9,6 +12,7 @@ def __init__( prob_end=None, edges=None, match_attributes=None, + seed=None ): """ A class to generate matches. This is used by the Tournament class which @@ -43,6 +47,7 @@ def __init__( self.opponents = players self.prob_end = prob_end self.match_attributes = match_attributes + self.random_generator = BulkRandomGenerator(seed) self.edges = edges if edges is not None: @@ -73,7 +78,8 @@ def build_match_chunks(self): for index_pair in edges: match_params = self.build_single_match_params() - yield (index_pair, match_params, self.repetitions) + r = next(self.random_generator) + yield (index_pair, match_params, self.repetitions, r) def build_single_match_params(self): """ diff --git a/axelrod/moran.py b/axelrod/moran.py index 1c0bdc487..44c11a214 100644 --- a/axelrod/moran.py +++ b/axelrod/moran.py @@ -1,6 +1,5 @@ """Implementation of the Moran process on Graphs.""" -import random from collections import Counter from typing import Callable, List, Optional, Set, Tuple @@ -11,35 +10,7 @@ from .deterministic_cache import DeterministicCache from .graph import Graph, complete_graph from .match import Match -from .random_ import randrange - - -def fitness_proportionate_selection( - scores: List, fitness_transformation: Callable = None -) -> int: - """Randomly selects an individual proportionally to score. - - Parameters - ---------- - scores: Any sequence of real numbers - fitness_transformation: A function mapping a score to a (non-negative) float - - Returns - ------- - An index of the above list selected at random proportionally to the list - element divided by the total. - """ - if fitness_transformation is None: - csums = np.cumsum(scores) - else: - csums = np.cumsum([fitness_transformation(s) for s in scores]) - total = csums[-1] - r = random.random() * total - - for i, x in enumerate(csums): - if x >= r: - break - return i +from .random_ import RandomGenerator class MoranProcess(object): @@ -57,7 +28,8 @@ def __init__( reproduction_graph: Graph = None, fitness_transformation: Callable = None, mutation_method="transition", - stop_on_fixation=True + stop_on_fixation=True, + seed = None ) -> None: """ An agent based Moran process class. In each round, each player plays a @@ -128,6 +100,7 @@ def __init__( self.winning_strategy_name = None # type: Optional[str] self.mutation_rate = mutation_rate self.stop_on_fixation = stop_on_fixation + self._random = RandomGenerator(seed=seed) m = mutation_method.lower() if m in ["atomic", "transition"]: self.mutation_method = m @@ -182,6 +155,32 @@ def set_players(self) -> None: self.players.append(player) self.populations = [self.population_distribution()] + def fitness_proportionate_selection(self, + scores: List, fitness_transformation: Callable = None) -> int: + """Randomly selects an individual proportionally to score. + + Parameters + ---------- + scores: Any sequence of real numbers + fitness_transformation: A function mapping a score to a (non-negative) float + + Returns + ------- + An index of the above list selected at random proportionally to the list + element divided by the total. + """ + if fitness_transformation is None: + csums = np.cumsum(scores) + else: + csums = np.cumsum([fitness_transformation(s) for s in scores]) + total = csums[-1] + r = self._random.random() * total + + for i, x in enumerate(csums): + if x >= r: + break + return i + def mutate(self, index: int) -> Player: """Mutate the player at index. @@ -199,10 +198,10 @@ def mutate(self, index: int) -> Player: # Assuming mutation_method == "transition" if self.mutation_rate > 0: # Choose another strategy at random from the initial population - r = random.random() + r = self._random.random() if r < self.mutation_rate: s = str(self.players[index]) - j = randrange(0, len(self.mutation_targets[s])) + j = self._random.randrange(0, len(self.mutation_targets[s])) p = self.mutation_targets[s][j] return p.clone() # Just clone the player @@ -223,13 +222,13 @@ def death(self, index: int = None) -> int: """ if index is None: # Select a player to be replaced globally - i = randrange(0, len(self.players)) + i = self._random.randrange(0, len(self.players)) # Record internally for use in _matchup_indices self.dead = i else: # Select locally # index is not None in this case - vertex = random.choice( + vertex = self._random.choice( sorted(self.reproduction_graph.out_vertices(self.locations[index])) ) i = self.index[vertex] diff --git a/axelrod/player.py b/axelrod/player.py index 61cc609af..d076612ae 100644 --- a/axelrod/player.py +++ b/axelrod/player.py @@ -9,22 +9,11 @@ from axelrod.action import Action from axelrod.game import DefaultGame from axelrod.history import History -from axelrod.random_ import random_flip +from axelrod.random_ import RandomGenerator C, D = Action.C, Action.D -def simultaneous_play(player, coplayer, noise=0): - """This pits two players against each other.""" - s1, s2 = player.strategy(coplayer), coplayer.strategy(player) - if noise: - s1 = random_flip(s1, noise) - s2 = random_flip(s2, noise) - player.update_history(s1, s2) - coplayer.update_history(s2, s1) - return s1, s2 - - class Player(object): """A class for a player in the tournament. @@ -64,6 +53,7 @@ def __init__(self): self._history = History() self.classifier = copy.deepcopy(self.classifier) self.set_match_attributes() + self.set_seed() def __eq__(self, other): """ @@ -77,6 +67,10 @@ def __eq__(self, other): value = getattr(self, attribute, None) other_value = getattr(other, attribute, None) + if attribute == "_random": + # Don't compare the random seeds. + continue + if isinstance(value, np.ndarray): if not (np.array_equal(value, other_value)): return False @@ -123,6 +117,11 @@ def set_match_attributes(self, length=-1, game=None, noise=0): self.match_attributes = {"length": length, "game": game, "noise": noise} self.receive_match_attributes() + def set_seed(self, seed=None): + """Set a random seed for the player's random number + generator.""" + self._random = RandomGenerator(seed=seed) + def __repr__(self): """The string method for the strategy. Appends the `__init__` parameters to the strategy's name.""" @@ -147,9 +146,9 @@ def strategy(self, opponent): """This is a placeholder strategy.""" raise NotImplementedError() - def play(self, opponent, noise=0): - """This pits two players against each other.""" - return simultaneous_play(self, opponent, noise) + # def play(self, opponent, noise=0): + # """This pits two players against each other.""" + # return simultaneous_play(self, opponent, noise) def clone(self): """Clones the player without history, reapplying configuration diff --git a/axelrod/random_.py b/axelrod/random_.py index 183c77b5f..dcf081da6 100644 --- a/axelrod/random_.py +++ b/axelrod/random_.py @@ -1,95 +1,138 @@ -import random - import numpy as np -from numpy.random import choice +from numpy.random import RandomState from axelrod.action import Action C, D = Action.C, Action.D -def seed(seed_): - """Sets a seed""" - random.seed(seed_) - np.random.seed(seed_) +class RandomGenerator(object): + """Container around a random number generator. + Enables reproducibility of player behavior, matches, + and tournaments.""" + def __init__(self, seed=None): + # self.random = random.Random() + # _random is the internal object that generators random values + self._random = RandomState() + self.original_seed = seed + self.seed(seed) + def seed(self, seed_): + """Sets a seed""" + self._random.seed(seed_) -def random_choice(p: float = 0.5) -> Action: - """ - Return C with probability `p`, else return D + def random(self, *args, **kwargs): + return self._random.rand(*args, **kwargs) - No random sample is carried out if p is 0 or 1. + def randint(self, *args, **kwargs): + return self._random.randint(*args, **kwargs) - Parameters - ---------- - p : float - The probability of picking C + def choice(self, *args, **kwargs): + return self._random.choice(*args, **kwargs) - Returns - ------- - axelrod.Action - """ - if p == 0: - return D + def uniform(self, *args, **kwargs): + return self._random.uniform(*args, **kwargs) + + def random_choice(self, p: float = 0.5) -> Action: + """ + Return C with probability `p`, else return D - if p == 1: - return C + No random sample is carried out if p is 0 or 1. - r = random.random() - if r < p: - return C - return D + Parameters + ---------- + p : float + The probability of picking C + Returns + ------- + axelrod.Action + """ + if p == 0: + return D -def random_flip(action: Action, threshold: float) -> Action: - """ - Return flipped action with probability `threshold` + if p == 1: + return C - No random sample is carried out if threshold is 0 or 1. + r = self.random() + if r < p: + return C + return D - Parameters - ---------- - action: - The action to flip or not - threshold : float - The probability of flipping action + def random_flip(self, action: Action, threshold: float) -> Action: + """ + Return flipped action with probability `threshold` - Returns - ------- - axelrod.Action - """ - if random_choice(threshold) == C: - return action.flip() - return action + No random sample is carried out if threshold is 0 or 1. + Parameters + ---------- + action: + The action to flip or not + threshold : float + The probability of flipping action -def randrange(a: int, b: int) -> int: - """Python 2 / 3 compatible randrange. Returns a random integer uniformly - between a and b (inclusive)""" - c = b - a - r = c * random.random() - return a + int(r) + Returns + ------- + axelrod.Action + """ + if self.random_choice(threshold) == C: + return action.flip() + return action + def randrange(self, a: int, b: int) -> int: + """Python 2 / 3 compatible randrange. Returns a random integer uniformly + between a and b (inclusive)""" + c = b - a + r = c * self.random() + return a + int(r) -def random_vector(size): - """Create a random vector of values in [0, 1] that sums to 1.""" - vector = np.random.random(size) - return vector / np.sum(vector) + def random_vector(self, size): + """Create a random vector of values in [0, 1] that sums to 1.""" + vector = self.random(size) + return np.array(vector) / np.sum(vector) class Pdf(object): """A class for a probability distribution""" - def __init__(self, counter): + def __init__(self, counter, seed=None): """Take as an instance of collections.counter""" self.sample_space, self.counts = zip(*counter.items()) self.size = len(self.sample_space) self.total = sum(self.counts) self.probability = list([v / self.total for v in self.counts]) + self._random = RandomGenerator(seed=seed) def sample(self): """Sample from the pdf""" - index = choice(a=range(self.size), p=self.probability) + index = self._random.choice(a=range(self.size), p=self.probability) # Numpy cannot sample from a list of n dimensional objects for n > 1, # need to sample an index. return self.sample_space[index] + + +class BulkRandomGenerator(object): + """Bulk generator of random integers for tournament seeding + and reproducibility. Use like a generator.""" + def __init__(self, seed=None, batch_size=1000): + self.random_generator = RandomState() + self.random_generator.seed(seed) + self._ints = None + self.batch_size = batch_size + self._fill_ints() + + def _fill_ints(self): + ints = self.random_generator.randint( + low=0, + high=2**32 - 1, + size=self.batch_size) + self._ints = (x for x in ints) + + def __next__(self): + try: + x = next(self._ints) + return x + except StopIteration: + self._fill_ints() + return next(self._ints) diff --git a/axelrod/strategies/adaptor.py b/axelrod/strategies/adaptor.py index 2648b2704..6eb7e2961 100644 --- a/axelrod/strategies/adaptor.py +++ b/axelrod/strategies/adaptor.py @@ -2,7 +2,6 @@ from axelrod.action import Action from axelrod.player import Player -from axelrod.random_ import random_choice from numpy import heaviside @@ -56,7 +55,7 @@ def strategy(self, opponent: Player) -> Action: p = self.perr + (1.0 - 2 * self.perr) * ( heaviside(self.s + 1, 1) - heaviside(self.s - 1, 1)) # Draw action - action = random_choice(p) + action = self._random.random_choice(p) return action diff --git a/axelrod/strategies/averagecopier.py b/axelrod/strategies/averagecopier.py index 597407c61..f59744e91 100644 --- a/axelrod/strategies/averagecopier.py +++ b/axelrod/strategies/averagecopier.py @@ -1,6 +1,5 @@ from axelrod.action import Action from axelrod.player import Player -from axelrod.random_ import random_choice C, D = Action.C, Action.D @@ -29,9 +28,9 @@ class AverageCopier(Player): def strategy(self, opponent: Player) -> Action: if len(opponent.history) == 0: # Randomly picks a strategy (not affected by history). - return random_choice(0.5) + return self._random.random_choice(0.5) p = opponent.cooperations / len(opponent.history) - return random_choice(p) + return self._random.random_choice(p) class NiceAverageCopier(Player): @@ -58,4 +57,4 @@ def strategy(self, opponent: Player) -> Action: if len(opponent.history) == 0: return C p = opponent.cooperations / len(opponent.history) - return random_choice(p) + return self._random.random_choice(p) diff --git a/axelrod/strategies/axelrod_first.py b/axelrod/strategies/axelrod_first.py index f9dab0b94..a606ed943 100644 --- a/axelrod/strategies/axelrod_first.py +++ b/axelrod/strategies/axelrod_first.py @@ -13,12 +13,10 @@ been made they are explained in the strategy docstrings. """ -import random from typing import Dict, List, Tuple, Optional from axelrod.action import Action from axelrod.player import Player -from axelrod.random_ import random_choice from axelrod.strategy_transformers import FinalTransformer from scipy.stats import chisquare @@ -355,7 +353,7 @@ def strategy(self, opponent: Player) -> Action: if opponent.history[-1] == D: return D p = self._cooperation_probability() - return random_choice(p) + return self._random.random_choice(p) class FirstByGraaskamp(Player): @@ -450,11 +448,11 @@ def strategy(self, opponent: Player) -> Action: return C if self.next_random_defection_turn is None: - self.next_random_defection_turn = random.randint(5, 15) + len(self.history) + self.next_random_defection_turn = self._random.randint(5, 15) + len(self.history) if len(self.history) == self.next_random_defection_turn: # resample the next defection turn - self.next_random_defection_turn = random.randint(5, 15) + len(self.history) + self.next_random_defection_turn = self._random.randint(5, 15) + len(self.history) return D return C @@ -485,10 +483,11 @@ class FirstByGrofman(Player): "manipulates_source": False, "manipulates_state": False, } + def strategy(self, opponent: Player) -> Action: if len(self.history) == 0 or self.history[-1] == opponent.history[-1]: return C - return random_choice(2 / 7) + return self._random.random_choice(2 / 7) class FirstByJoss(MemoryOnePlayer): @@ -519,7 +518,7 @@ def __init__(self, p: float = 0.9) -> None: or (D, C), i.e. the opponent cooperated. """ four_vector = (p, 0, p, 0) - self.p = p + # self.p = p super().__init__(four_vector) @@ -763,7 +762,7 @@ def strategy(self, opponent: Player) -> Action: cooperate_count = opponent.history[-rounds:].count(C) prop_cooperate = cooperate_count / rounds prob_cooperate = max(0, prop_cooperate - 0.10) - return random_choice(prob_cooperate) + return self._random.random_choice(prob_cooperate) class FirstByAnonymous(Player): @@ -801,10 +800,9 @@ class FirstByAnonymous(Player): "manipulates_state": False, } - @staticmethod - def strategy(opponent: Player) -> Action: - r = random.uniform(3, 7) / 10 - return random_choice(r) + def strategy(self, opponent: Player) -> Action: + r = self._random.uniform(3, 7) / 10 + return self._random.random_choice(r) @FinalTransformer((D, D), name_prefix=None) diff --git a/axelrod/strategies/axelrod_second.py b/axelrod/strategies/axelrod_second.py index 50e4e18ee..1ed19269b 100644 --- a/axelrod/strategies/axelrod_second.py +++ b/axelrod/strategies/axelrod_second.py @@ -4,14 +4,12 @@ tournament by the given author. """ -import random from typing import List import numpy as np from axelrod.action import Action from axelrod.interaction_utils import compute_final_score from axelrod.player import Player -from axelrod.random_ import random_choice from axelrod.strategies.finite_state_machines import FSMPlayer C, D = Action.C, Action.D @@ -56,11 +54,12 @@ def strategy(self, opponent: Player) -> Action: # Now cooperate unless all of the necessary conditions are true defection_prop = opponent.defections / len(opponent.history) if opponent.history[-1] == D: - r = random.random() + r = self._random.random() if defection_prop >= max(0.4, r): return D return C + class SecondByEatherley(Player): """ Strategy submitted to Axelrod's second tournament by Graham Eatherley. @@ -86,8 +85,7 @@ class SecondByEatherley(Player): "manipulates_state": False, } - @staticmethod - def strategy(opponent: Player) -> Action: + def strategy(self, opponent: Player) -> Action: # Cooperate on the first move if not len(opponent.history): return C @@ -97,7 +95,7 @@ def strategy(opponent: Player) -> Action: # Respond to defections with probability equal to opponent's total # proportion of defections defection_prop = opponent.defections / len(opponent.history) - return random_choice(1 - defection_prop) + return self._random.random_choice(1 - defection_prop) class SecondByTester(Player): @@ -403,7 +401,7 @@ def strategy(self, opponent: Player) -> Action: + (1 / (((len(self.history)) + 1) ** 2)) - (self.dict[opponent.history[-1]] / 4) ) - if random.random() <= probability: + if self._random.random() <= probability: return C self.num_turns_after_good_defection = 1 return D @@ -414,7 +412,7 @@ def strategy(self, opponent: Player) -> Action: + ((current_score[0] - current_score[1]) / 100) + (4 / ((len(self.history)) + 1)) ) - if random.random() <= probability: + if self._random.random() <= probability: return C return D return opponent.history[-1] @@ -565,7 +563,7 @@ def strategy(self, opponent: Player) -> Action: if one_move_ago == two_moves_ago and two_moves_ago == three_moves_ago: return one_move_ago - r = random.random() # Everything following is stochastic + r = self._random.random() # Everything following is stochastic if one_move_ago == two_moves_ago: if r < 0.9: return one_move_ago @@ -791,7 +789,7 @@ def strategy(self, opponent: Player) -> Action: if number_defects > 17: return D else: - return random_choice(0.5) + return self._random.random_choice(0.5) else: return C @@ -832,7 +830,7 @@ def strategy(self, opponent: Player) -> Action: if number_defects in [4, 7, 9]: return D if number_defects > 9 and opponent.history[-1] == D: - return random_choice((0.5) ** (number_defects - 9)) + return self._random.random_choice((0.5) ** (number_defects - 9)) return C @@ -1330,7 +1328,7 @@ def strategy(self, opponent: Player) -> Action: # Defect once on turn 37 (if no streaks) self.more_coop, self.last_generous_n_turns_ago = 2, 1 return self.try_return(D, lower_flags=False) - if self.burned or random.random() > self.prob: + if self.burned or self._random.random() > self.prob: # Tit-for-Tat with probability 1-`prob` return self.try_return(opponent.history[-1], inc_parity=True) @@ -1486,7 +1484,7 @@ def strategy(self, opponent: Player) -> Action: self.flack += 1 if opponent.history[-1] == D else 0 self.flack *= 0.5 # Defections have half-life of one round - return random_choice(1.0 - self.flack) + return self._random.random_choice(1.0 - self.flack) class SecondByLeyvraz(Player): @@ -1538,7 +1536,7 @@ def strategy(self, opponent: Player) -> Action: if len(opponent.history) >= go_back: recent_history[-go_back] = opponent.history[-go_back] - return random_choice( + return self._random.random_choice( self.prob_coop[(recent_history[-3], recent_history[-2], recent_history[-1])] ) @@ -1622,7 +1620,7 @@ def strategy(self, opponent: Player) -> Action: did_d = np.vectorize(lambda action: int(action == D)) number_defects = np.sum(did_d(recent_history)) - return random_choice(self.prob_coop[number_defects]) + return self._random.random_choice(self.prob_coop[number_defects]) class SecondByRichardHufford(Player): @@ -2128,4 +2126,4 @@ def strategy(self, opponent: Player) -> Action: # what we know two turns ago. prob_coop = self.opp_c_after_x[us_two_turns_ago] / self.total_num_of_x[ us_two_turns_ago] - return random_choice(prob_coop) + return self._random.random_choice(prob_coop) diff --git a/axelrod/strategies/better_and_better.py b/axelrod/strategies/better_and_better.py index 1af697a00..02db3e9e8 100644 --- a/axelrod/strategies/better_and_better.py +++ b/axelrod/strategies/better_and_better.py @@ -1,6 +1,5 @@ from axelrod.action import Action from axelrod.player import Player -from axelrod.random_ import random_choice C, D = Action.C, Action.D @@ -29,4 +28,4 @@ class BetterAndBetter(Player): def strategy(self, opponent: Player) -> Action: current_round = len(self.history) + 1 probability = current_round / 1000 - return random_choice(probability) + return self._random.random_choice(probability) diff --git a/axelrod/strategies/bush_mosteller.py b/axelrod/strategies/bush_mosteller.py index d6ed5adf3..8b997eb91 100644 --- a/axelrod/strategies/bush_mosteller.py +++ b/axelrod/strategies/bush_mosteller.py @@ -1,6 +1,3 @@ -import random - -from axelrod import random_choice from axelrod.action import Action from axelrod.player import Player @@ -124,9 +121,9 @@ def strategy(self, opponent: Player) -> Action: # First turn if len(self.history) == 0: - return random_choice(self._c_prob / (self._c_prob + self._d_prob)) + return self._random.random_choice(self._c_prob / (self._c_prob + self._d_prob)) # Updating stimulus depending on his own latest choice self.stimulus_update(opponent) - return random_choice(self._c_prob / (self._c_prob + self._d_prob)) + return self._random.random_choice(self._c_prob / (self._c_prob + self._d_prob)) diff --git a/axelrod/strategies/calculator.py b/axelrod/strategies/calculator.py index 8ac9b59d0..07e27d33c 100644 --- a/axelrod/strategies/calculator.py +++ b/axelrod/strategies/calculator.py @@ -30,8 +30,12 @@ class Calculator(Player): } def __init__(self) -> None: - super().__init__() self.joss_instance = Joss() + super().__init__() + + def set_seed(self, seed=None): + super().set_seed(seed) + self.joss_instance.set_seed(seed) def strategy(self, opponent: Player) -> Action: turn = len(self.history) diff --git a/axelrod/strategies/gambler.py b/axelrod/strategies/gambler.py index f127ce0f4..578386441 100644 --- a/axelrod/strategies/gambler.py +++ b/axelrod/strategies/gambler.py @@ -7,12 +7,10 @@ import random from typing import Any -from axelrod.action import Action, str_to_actions, actions_to_str +from axelrod.action import Action from axelrod.load_data_ import load_pso_tables from axelrod.player import Player -from axelrod.random_ import random_choice - from .lookerup import EvolvableLookerUp, LookupTable, LookerUp, Plays, create_lookup_table_keys C, D = Action.C, Action.D @@ -44,7 +42,7 @@ def strategy(self, opponent: Player) -> Action: actions_or_float = super(Gambler, self).strategy(opponent) if isinstance(actions_or_float, Action): return actions_or_float - return random_choice(actions_or_float) + return self._random.random_choice(actions_or_float) class EvolvableGambler(Gambler, EvolvableLookerUp): @@ -87,10 +85,14 @@ def __init__( @classmethod def random_value(cls): + # TODO: For reproducibility, this invocation of random may need to be folded into the + # evolutionary algorithm class, once it is merged into Axelrod. return random.random() @classmethod def mutate_value(cls, value): + # TODO: For reproducibility, this invocation of random may need to be folded into the + # evolutionary algorithm class, once it is merged into Axelrod. ep = random.uniform(-1, 1) / 4 value += ep if value < 0: diff --git a/axelrod/strategies/geller.py b/axelrod/strategies/geller.py index 8343f8aa1..57277842e 100644 --- a/axelrod/strategies/geller.py +++ b/axelrod/strategies/geller.py @@ -7,7 +7,6 @@ from axelrod._strategy_utils import inspect_strategy from axelrod.action import Action from axelrod.player import Player -from axelrod.random_ import random_choice C, D = Action.C, Action.D @@ -49,10 +48,9 @@ class Geller(Player): "manipulates_state": False, } - @staticmethod - def foil_strategy_inspection() -> Action: + def foil_strategy_inspection(self) -> Action: """Foils _strategy_utils.inspect_strategy and _strategy_utils.look_ahead""" - return random_choice(0.5) + return self._random.random_choice(0.5) def strategy(self, opponent: Player) -> Action: """ diff --git a/axelrod/strategies/hmm.py b/axelrod/strategies/hmm.py index 8ae2ed811..09f384d73 100644 --- a/axelrod/strategies/hmm.py +++ b/axelrod/strategies/hmm.py @@ -1,11 +1,12 @@ -from random import randrange -import numpy.random as random -from numpy.random import choice - from axelrod.action import Action from axelrod.evolvable_player import EvolvablePlayer, InsufficientParametersError, copy_lists, crossover_lists from axelrod.player import Player -from axelrod.random_ import random_choice, random_vector +from axelrod.random_ import RandomGenerator + +# This instance is for the mutation methods. For reproducibility, +# random seed propagation should be refactored in the evolutionary +# algorithms. +random = RandomGenerator() C, D = Action.C, Action.D @@ -57,7 +58,8 @@ class SimpleHMM(object): """ def __init__( - self, transitions_C, transitions_D, emission_probabilities, initial_state + self, transitions_C, transitions_D, emission_probabilities, initial_state, + seed=None ) -> None: """ Params @@ -71,6 +73,7 @@ def __init__( self.transitions_D = transitions_D self.emission_probabilities = emission_probabilities self.state = initial_state + self._random = RandomGenerator(seed=seed) def is_well_formed(self) -> bool: """ @@ -111,12 +114,12 @@ def move(self, opponent_action: Action) -> Action: """ num_states = len(self.emission_probabilities) if opponent_action == C: - next_state = choice(num_states, 1, p=self.transitions_C[self.state]) + next_state = self._random.choice(num_states, 1, p=self.transitions_C[self.state]) else: - next_state = choice(num_states, 1, p=self.transitions_D[self.state]) + next_state = self._random.choice(num_states, 1, p=self.transitions_D[self.state]) self.state = next_state[0] p = self.emission_probabilities[self.state] - action = random_choice(p) + action = self._random.random_choice(p) return action @@ -158,7 +161,8 @@ def __init__( self.initial_state = initial_state self.initial_action = initial_action self.hmm = SimpleHMM( - copy_lists(transitions_C), copy_lists(transitions_D), list(emission_probabilities), initial_state + copy_lists(transitions_C), copy_lists(transitions_D), list(emission_probabilities), initial_state, + seed=self._random.randint(0, 10000000) ) assert self.hmm.is_well_formed() self.state = self.hmm.state @@ -247,10 +251,10 @@ def random_params(cls, num_states): transitions_D = [] emission_probabilities = [] for _ in range(num_states): - transitions_C.append(random_vector(num_states)) - transitions_D.append(random_vector(num_states)) + transitions_C.append(random.random_vector(num_states)) + transitions_D.append(random.random_vector(num_states)) emission_probabilities.append(random.random()) - initial_state = randrange(num_states) + initial_state = random.randrange(num_states) initial_action = C return transitions_C, transitions_D, emission_probabilities, initial_state, initial_action @@ -277,7 +281,7 @@ def mutate(self): initial_action = self.initial_action.flip() initial_state = self.initial_state if random.random() < self.mutation_probability / (10 * self.num_states): - initial_state = randrange(self.num_states) + initial_state = random.randrange(self.num_states) return self.create_new( transitions_C=transitions_C, transitions_D=transitions_D, diff --git a/axelrod/strategies/inverse.py b/axelrod/strategies/inverse.py index 092309330..5728238b3 100644 --- a/axelrod/strategies/inverse.py +++ b/axelrod/strategies/inverse.py @@ -1,6 +1,5 @@ from axelrod.action import Action from axelrod.player import Player -from axelrod.random_ import random_choice C, D = Action.C, Action.D @@ -25,8 +24,7 @@ class Inverse(Player): "manipulates_state": False, } - @staticmethod - def strategy(opponent: Player) -> Action: + def strategy(self, opponent: Player) -> Action: """Looks at opponent history to see if they have defected. If so, player defection is inversely proportional to when this occurred. @@ -45,4 +43,4 @@ def strategy(opponent: Player) -> Action: if index is None: return C - return random_choice(1 - 1 / abs(index)) + return self._random.random_choice(1 - 1 / abs(index)) diff --git a/axelrod/strategies/memoryone.py b/axelrod/strategies/memoryone.py index 8fa4aeae6..90c0ad2e1 100644 --- a/axelrod/strategies/memoryone.py +++ b/axelrod/strategies/memoryone.py @@ -6,7 +6,6 @@ from axelrod.action import Action from axelrod.player import Player -from axelrod.random_ import random_choice C, D = Action.C, Action.D @@ -92,7 +91,7 @@ def strategy(self, opponent: Player) -> Action: # Determine which probability to use p = self._four_vector[(self.history[-1], opponent.history[-1])] # Draw a random number in [0, 1] to decide - return random_choice(p) + return self._random.random_choice(p) class WinStayLoseShift(MemoryOnePlayer): @@ -321,7 +320,7 @@ class ALLCorALLD(Player): def strategy(self, opponent: Player) -> Action: if len(self.history) == 0: - return random_choice(0.6) + return self._random.random_choice(0.6) return self.history[-1] diff --git a/axelrod/strategies/memorytwo.py b/axelrod/strategies/memorytwo.py index 3466256b3..8bb050416 100644 --- a/axelrod/strategies/memorytwo.py +++ b/axelrod/strategies/memorytwo.py @@ -6,7 +6,6 @@ from axelrod.action import Action from axelrod.player import Player -from axelrod.random_ import random_choice from .defector import Defector from .titfortat import TitFor2Tats, TitForTat @@ -103,7 +102,7 @@ def strategy(self, opponent: Player) -> Action: (tuple(self.history[-2:]), tuple(opponent.history[-2:])) ] # Draw a random number in [0, 1] to decide - return random_choice(p) + return self._random.random_choice(p) class AON2(MemoryTwoPlayer): @@ -127,7 +126,7 @@ class AON2(MemoryTwoPlayer): In [Hilbe2017]_ the following vectors are reported as "equivalent" to AON2 with their respective self-cooperation rate (note that these are not the same): - + 1. [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], self-cooperation rate: 0.952 2. [1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], self-cooperation diff --git a/axelrod/strategies/meta.py b/axelrod/strategies/meta.py index 09bebd26b..442a4dc39 100644 --- a/axelrod/strategies/meta.py +++ b/axelrod/strategies/meta.py @@ -1,7 +1,5 @@ -import random import numpy as np -from numpy.random import choice from axelrod.action import Action from axelrod.classifier import Classifiers @@ -60,6 +58,7 @@ def __init__(self, team=None): self.team = [t() for t in self.team] super().__init__() + self.set_seed() # This player inherits the classifiers of its team. # Note that memory_depth is not simply the max memory_depth of the team. @@ -76,6 +75,11 @@ def __init__(self, team=None): self._last_results = None + def set_seed(self, seed=None): + super().set_seed(seed) + for t in self.team: + t.set_seed(self._random.randint(0, 100000000)) + def receive_match_attributes(self): for t in self.team: t.set_match_attributes(**self.match_attributes) @@ -210,7 +214,7 @@ def meta_strategy(self, results, opponent): # Choose one of the best scorers at random scores.sort(reverse=True) prop = max(1, int(len(scores) * 0.08)) - index = choice([i for (s, i) in scores[:prop]]) + index = self._random.choice([i for (s, i) in scores[:prop]]) return results[index] @@ -498,7 +502,7 @@ def __init__(self, team=None, distribution=None): def meta_strategy(self, results, opponent): """Using the numpy.random choice function to sample with weights""" - return choice(results, p=self.distribution) + return self._random.choice(results, p=self.distribution) class NMWEDeterministic(NiceMetaWinnerEnsemble): @@ -654,14 +658,14 @@ def memory_alter(self): """ Alters memory entry, i.e. puts C if there's a D and vice versa. """ - alter = choice(range(0, len(self.memory))) + alter = self._random.choice(range(0, len(self.memory))) self.memory[alter] = self.memory[alter].flip() def memory_delete(self): """ Deletes memory entry. """ - self.memory.pop(choice(range(0, len(self.memory)))) + self.memory.pop(self._random.choice(range(0, len(self.memory)))) def meta_strategy(self, results, opponent): try: @@ -671,9 +675,9 @@ def meta_strategy(self, results, opponent): if len(self.history) < self.start_strategy_duration: return results[0] else: - if random.random() <= self.p_memory_alter: + if self._random.random() <= self.p_memory_alter: self.memory_alter() - if random.random() <= self.p_memory_delete: + if self._random.random() <= self.p_memory_delete: self.memory_delete() self.gain_loss_translate() if sum(self.gloss_values) < 0: diff --git a/axelrod/strategies/mutual.py b/axelrod/strategies/mutual.py index d537bbaad..34a7e6144 100644 --- a/axelrod/strategies/mutual.py +++ b/axelrod/strategies/mutual.py @@ -1,6 +1,5 @@ from axelrod.action import Action from axelrod.player import Player -from axelrod.random_ import random_choice C, D = Action.C, Action.D @@ -25,7 +24,7 @@ class Desperate(Player): def strategy(self, opponent: Player) -> Action: if not opponent.history: - return random_choice() + return self._random.random_choice() if self.history[-1] == D and opponent.history[-1] == D: return C return D @@ -51,7 +50,7 @@ class Hopeless(Player): def strategy(self, opponent: Player) -> Action: if not opponent.history: - return random_choice() + return self._random.random_choice() if self.history[-1] == C and opponent.history[-1] == C: return D return C @@ -77,7 +76,7 @@ class Willing(Player): def strategy(self, opponent: Player) -> Action: if not opponent.history: - return random_choice() + return self._random.random_choice() if self.history[-1] == D and opponent.history[-1] == D: return D return C diff --git a/axelrod/strategies/negation.py b/axelrod/strategies/negation.py index cccf218a1..89943a19d 100644 --- a/axelrod/strategies/negation.py +++ b/axelrod/strategies/negation.py @@ -1,6 +1,5 @@ from axelrod.action import Action from axelrod.player import Player -from axelrod.random_ import random_choice C, D = Action.C, Action.D @@ -29,6 +28,6 @@ class Negation(Player): def strategy(self, opponent: Player) -> Action: # Random first move if not self.history: - return random_choice() + return self._random.random_choice() # Act opposite of opponent otherwise return opponent.history[-1].flip() diff --git a/axelrod/strategies/prober.py b/axelrod/strategies/prober.py index 96940a1fc..6a5829bea 100644 --- a/axelrod/strategies/prober.py +++ b/axelrod/strategies/prober.py @@ -1,9 +1,7 @@ -import random from typing import List from axelrod.action import Action from axelrod.player import Player -from axelrod.random_ import random_choice Vector = List[float] @@ -349,7 +347,7 @@ def strategy(self, opponent: Player) -> Action: if opponent.history[-1] == D: return D # Otherwise cooperate, defect with probability 1 - self.p - choice = random_choice(1 - self.p) + choice = self._random.random_choice(1 - self.p) return choice @@ -397,7 +395,7 @@ def strategy(self, opponent: Player) -> Action: return D # Otherwise cooperate with probability 1 - self.p - if random.random() < 1 - self.p: + if self._random.random() < 1 - self.p: self.probing = False return C diff --git a/axelrod/strategies/qlearner.py b/axelrod/strategies/qlearner.py index dd308feb6..f925ccdf0 100644 --- a/axelrod/strategies/qlearner.py +++ b/axelrod/strategies/qlearner.py @@ -1,10 +1,8 @@ -import random from collections import OrderedDict -from typing import Dict, List, Union +from typing import Dict, Union from axelrod.action import Action, actions_to_str from axelrod.player import Player -from axelrod.random_ import random_choice Score = Union[int, float] @@ -61,7 +59,7 @@ def receive_match_attributes(self): def strategy(self, opponent: Player) -> Action: """Runs a qlearn algorithm while the tournament is running.""" if len(self.history) == 0: - self.prev_action = random_choice() + self.prev_action = self._random.random_choice() self.original_prev_action = self.prev_action state = self.find_state(opponent) reward = self.find_reward(opponent) @@ -78,11 +76,11 @@ def select_action(self, state: str) -> Action: """ Selects the action based on the epsilon-soft policy """ - rnd_num = random.random() + rnd_num = self._random.random() p = 1.0 - self.action_selection_parameter if rnd_num < p: return max(self.Qs[state], key=lambda x: self.Qs[state][x]) - return random_choice() + return self._random.random_choice() def find_state(self, opponent: Player) -> str: """ @@ -108,7 +106,7 @@ def find_reward(self, opponent: Player) -> Dict[Action, Dict[Action, Score]]: """ if len(opponent.history) == 0: - opp_prev_action = random_choice() + opp_prev_action = self._random.random_choice() else: opp_prev_action = opponent.history[-1] return self.payoff_matrix[self.prev_action][opp_prev_action] diff --git a/axelrod/strategies/rand.py b/axelrod/strategies/rand.py index eb259e37c..74b050af2 100644 --- a/axelrod/strategies/rand.py +++ b/axelrod/strategies/rand.py @@ -1,6 +1,5 @@ from axelrod.action import Action from axelrod.player import Player -from axelrod.random_ import random_choice class Random(Player): @@ -43,4 +42,4 @@ def __init__(self, p: float = 0.5) -> None: self.classifier["stochastic"] = False def strategy(self, opponent: Player) -> Action: - return random_choice(self.p) + return self._random.random_choice(self.p) diff --git a/axelrod/strategies/selfsteem.py b/axelrod/strategies/selfsteem.py index 59b43657b..43c663aea 100644 --- a/axelrod/strategies/selfsteem.py +++ b/axelrod/strategies/selfsteem.py @@ -2,7 +2,6 @@ from axelrod.action import Action from axelrod.player import Player -from axelrod.random_ import random_choice C, D = Action.C, Action.D @@ -48,6 +47,6 @@ def strategy(self, opponent: Player) -> Action: return opponent.history[-1] if sine_value < 0.3 and sine_value > -0.3: - return random_choice() + return self._random.random_choice() return C diff --git a/axelrod/strategies/stalker.py b/axelrod/strategies/stalker.py index bc533cfa3..df309f365 100644 --- a/axelrod/strategies/stalker.py +++ b/axelrod/strategies/stalker.py @@ -1,6 +1,5 @@ from axelrod.action import Action from axelrod.player import Player -from axelrod.random_ import random_choice from axelrod.strategy_transformers import FinalTransformer C, D = Action.C, Action.D @@ -75,4 +74,4 @@ def strategy(self, opponent: Player) -> Action: return C if (current_average_score < 2) and (current_average_score > 1): return D - return random_choice() + return self._random.random_choice() diff --git a/axelrod/strategies/titfortat.py b/axelrod/strategies/titfortat.py index cb3b79245..b91b80018 100644 --- a/axelrod/strategies/titfortat.py +++ b/axelrod/strategies/titfortat.py @@ -1,6 +1,5 @@ from axelrod.action import Action, actions_to_str from axelrod.player import Player -from axelrod.random_ import random_choice from axelrod.strategy_transformers import FinalTransformer, TrackHistoryTransformer C, D = Action.C, Action.D @@ -126,15 +125,14 @@ class DynamicTwoTitsForTat(Player): "manipulates_state": False, } - @staticmethod - def strategy(opponent): + def strategy(self, opponent): # First move if not opponent.history: # Make sure we cooperate first turn return C if D in opponent.history[-2:]: # Probability of cooperating regardless - return random_choice(opponent.cooperations / len(opponent.history)) + return self._random.random_choice(opponent.cooperations / len(opponent.history)) else: return C @@ -857,7 +855,7 @@ def strategy(self, opponent: Player) -> Action: if self.is_defector: return D if self.history[-1] == D and opponent.history[-1] == C: - decision = random_choice() + decision = self._random.random_choice() if decision == C: return C else: @@ -911,7 +909,7 @@ def strategy(self, opponent: Player) -> Action: if self.act_random: self.act_random = False - return random_choice(self.p) + return self._random.random_choice(self.p) self.act_random = True return opponent.history[-1] diff --git a/axelrod/strategies/worse_and_worse.py b/axelrod/strategies/worse_and_worse.py index cd80ca822..1bcb4d9c5 100644 --- a/axelrod/strategies/worse_and_worse.py +++ b/axelrod/strategies/worse_and_worse.py @@ -1,6 +1,5 @@ from axelrod.action import Action from axelrod.player import Player -from axelrod.random_ import random_choice C, D = Action.C, Action.D @@ -31,7 +30,7 @@ class WorseAndWorse(Player): def strategy(self, opponent: Player) -> Action: current_round = len(self.history) + 1 probability = 1 - current_round / 1000 - return random_choice(probability) + return self._random.random_choice(probability) class KnowledgeableWorseAndWorse(Player): @@ -58,7 +57,7 @@ def strategy(self, opponent: Player) -> Action: current_round = len(self.history) + 1 expected_length = self.match_attributes["length"] probability = 1 - current_round / expected_length - return random_choice(probability) + return self._random.random_choice(probability) class WorseAndWorse2(Player): @@ -91,7 +90,7 @@ def strategy(self, opponent: Player) -> Action: return opponent.history[-1] else: probability = 20 / current_round - return random_choice(probability) + return self._random.random_choice(probability) class WorseAndWorse3(Player): @@ -123,4 +122,4 @@ def strategy(self, opponent: Player) -> Action: return C else: probability = 1 - opponent.defections / (current_round - 1) - return random_choice(probability) + return self._random.random_choice(probability) diff --git a/axelrod/strategy_transformers.py b/axelrod/strategy_transformers.py index 6d50d77c2..b05df8a07 100644 --- a/axelrod/strategy_transformers.py +++ b/axelrod/strategy_transformers.py @@ -9,15 +9,11 @@ import copy import inspect from importlib import import_module -import random from typing import Any -from numpy.random import choice - from axelrod.strategies.sequence_player import SequencePlayer from .action import Action from .player import Player -from .random_ import random_choice C, D = Action.C, Action.D @@ -343,7 +339,7 @@ def dual_wrapper(player, opponent: Player, proposed_action: Action) -> Action: def noisy_wrapper(player, opponent, action, noise=0.05): """Flips the player's actions with probability: `noise`.""" - r = random.random() + r = player._random.random() if r < noise: return action.flip() return action @@ -365,7 +361,7 @@ def forgiver_wrapper(player, opponent, action, p): """If a strategy wants to defect, flip to cooperate with the given probability.""" if action == D: - return random_choice(p) + return player._random.random_choice(p) return C @@ -542,8 +538,8 @@ def mixed_wrapper(player, opponent, action, probability, m_player): if mutate_prob > 0: # Distribution of choice of mutation: normalised_prob = [prob / mutate_prob for prob in probability] - if random.random() < mutate_prob: - p = choice(list(m_player), p=normalised_prob)() + if player._random.random() < mutate_prob: + p = player._random.choice(list(m_player), p=normalised_prob)() p._history = player._history return p.strategy(opponent) @@ -606,7 +602,7 @@ def joss_ann_wrapper(player, opponent, proposed_action, probability): remaining_probability = max(0, 1 - probability[0] - probability[1]) probability += (remaining_probability,) options = [C, D, proposed_action] - action = choice(options, p=probability) + action = player._random.choice(options, p=probability) return action diff --git a/axelrod/tests/strategies/test_adaptive.py b/axelrod/tests/strategies/test_adaptive.py index 1fbca26f4..188408d14 100644 --- a/axelrod/tests/strategies/test_adaptive.py +++ b/axelrod/tests/strategies/test_adaptive.py @@ -2,7 +2,7 @@ import axelrod as axl -from .test_player import TestMatch, TestPlayer +from .test_player import TestPlayer C, D = axl.Action.C, axl.Action.D @@ -37,10 +37,11 @@ def test_strategy(self): def test_scoring(self): player = axl.Adaptive() opponent = axl.Cooperator() - player.play(opponent) - player.play(opponent) + match = axl.Match((player, opponent), turns=2, seed=9) + match.play() self.assertEqual(3, player.scores[C]) - game = axl.Game(-3, 10, 10, 10) - player.set_match_attributes(game=game) - player.play(opponent) + + match = axl.Match((player, opponent), turns=1, reset=True, seed=9, + game=axl.Game(-3, 10, 10, 10)) + match.play() self.assertEqual(0, player.scores[C]) diff --git a/axelrod/tests/strategies/test_adaptor.py b/axelrod/tests/strategies/test_adaptor.py index 740fdb252..5ec28f24a 100644 --- a/axelrod/tests/strategies/test_adaptor.py +++ b/axelrod/tests/strategies/test_adaptor.py @@ -1,10 +1,7 @@ """Tests for the adaptor""" -import unittest - import axelrod as axl - -from .test_player import TestPlayer, test_four_vector +from .test_player import TestPlayer C, D = axl.Action.C, axl.Action.D diff --git a/axelrod/tests/strategies/test_meta.py b/axelrod/tests/strategies/test_meta.py index 7256369c8..b9dbe9fef 100644 --- a/axelrod/tests/strategies/test_meta.py +++ b/axelrod/tests/strategies/test_meta.py @@ -82,12 +82,9 @@ def test_clone(self, seed): axl.Defector(), axl.TitForTat(), ]: - player1.reset() - player2.reset() for p in [player1, player2]: - axl.seed(seed) - m = axl.Match((p, op), turns=turns) - m.play() + match = axl.Match((p, op), turns=turns, reset=True, seed=seed) + match.play() self.assertEqual(len(player1.history), turns) self.assertEqual(player1.history, player2.history) diff --git a/axelrod/tests/strategies/test_player.py b/axelrod/tests/strategies/test_player.py index 2b4dd33c4..667924123 100644 --- a/axelrod/tests/strategies/test_player.py +++ b/axelrod/tests/strategies/test_player.py @@ -1,19 +1,20 @@ import unittest import itertools import pickle -import random import types import numpy as np +from hypothesis import given, settings +from hypothesis.strategies import integers, sampled_from + import axelrod as axl -from axelrod.player import simultaneous_play from axelrod.tests.property import strategy_lists -from hypothesis import given, settings -from hypothesis.strategies import integers, sampled_from C, D = axl.Action.C, axl.Action.D +random = axl.RandomGenerator() + short_run_time_short_mem = [ s for s in axl.short_run_time_strategies @@ -102,12 +103,12 @@ def test_state_distribution(self): ) def test_noisy_play(self): - axl.seed(1) noise = 0.2 player1, player2 = self.player(), self.player() player1.strategy = cooperate player2.strategy = defect - player1.play(player2, noise) + match = axl.Match((player1, player2), turns=1, seed=1) + match.play() self.assertEqual(player1.history[0], D) self.assertEqual(player2.history[0], D) @@ -141,12 +142,9 @@ def test_clone(self): player2 = player1.clone() turns = 50 for op in [axl.Cooperator(), axl.Defector(), axl.TitForTat()]: - player1.reset() - player2.reset() seed = random.randint(0, 10 ** 6) for p in [player1, player2]: - axl.seed(seed) - m = axl.Match((p, op), turns=turns) + m = axl.Match((p, op), turns=turns, reset=True, seed=seed) m.play() self.assertEqual(len(player1.history), turns) self.assertEqual(player1.history, player2.history) @@ -415,9 +413,9 @@ def equality_of_players_test(self, p1, p2, seed, opponent): a2 = opponent() self.assertEqual(p1, p2) for player, op in [(p1, a1), (p2, a2)]: - axl.seed(seed) - for _ in range(10): - simultaneous_play(player, op) + m = axl.Match(players=(player, op), reset=True, seed=seed, + turns=10) + m.play() self.assertEqual(p1, p2) p1 = pickle.loads(pickle.dumps(p1)) p2 = pickle.loads(pickle.dumps(p2)) @@ -451,13 +449,10 @@ def test_reset_history_and_attributes(self): axl.Alternator(), axl.Cooperator(), ]: - player = self.player() clone = player.clone() - for seed in range(10): - axl.seed(seed) - player.play(opponent) - + match = axl.Match((player, opponent), turns=10, seed=111) + match.play() player.reset() self.assertEqual(player, clone) @@ -468,6 +463,17 @@ def test_reset_clone(self): clone = player.clone() self.assertEqual(player, clone) + def test_reproducibility_of_play(self): + player = self.player() + player_clone = player.clone() + coplayer = axelrod.Random(0.5) + coplayer_clone = coplayer.clone() + m1 = axelrod.Match((player, coplayer), turns=10, seed=10) + m2 = axelrod.Match((player_clone, coplayer_clone), turns=10, seed=10) + m1.play() + m2.play() + self.assertEqual(m1.result, m2.result) + @given(seed=integers(min_value=1, max_value=20000000)) @settings(max_examples=1) def test_clone(self, seed): @@ -484,19 +490,15 @@ def test_clone(self, seed): self.assertEqual(player2.classifier, player1.classifier) self.assertEqual(player2.match_attributes, player1.match_attributes) - turns = 50 - r = random.random() + turns = 20 for op in [ axl.Cooperator(), axl.Defector(), axl.TitForTat(), axl.Random(p=r), ]: - player1.reset() - player2.reset() for p in [player1, player2]: - axl.seed(seed) - m = axl.Match((p, op), turns=turns) + m = axl.Match((p, op), turns=turns, reset=True, seed=seed) m.play() self.assertEqual(len(player1.history), turns) self.assertEqual(player1.history, player2.history) @@ -582,9 +584,6 @@ def versus_test( if init_kwargs is None: init_kwargs = dict() - if seed is not None: - axl.seed(seed) - player = self.player(**init_kwargs) match = axl.Match( @@ -592,6 +591,7 @@ def versus_test( turns=turns, noise=noise, match_attributes=match_attributes, + seed=seed ) self.assertEqual(match.play(), expected_actions) @@ -654,18 +654,22 @@ def versus_test( """Tests a sequence of outcomes for two given players.""" if len(expected_actions1) != len(expected_actions2): raise ValueError("Mismatched History lengths.") - if seed: - axl.seed(seed) turns = len(expected_actions1) - match = axl.Match((player1, player2), turns=turns, noise=noise) + match = axl.Match((player1, player2), turns=turns, noise=noise, seed=seed) match.play() - # Test expected sequence of play. - for i, (outcome1, outcome2) in enumerate( - zip(expected_actions1, expected_actions2) - ): - player1.play(player2) - self.assertEqual(player1.history[i], outcome1) - self.assertEqual(player2.history[i], outcome2) + self.assertEqual(match.result, list(zip(expected_actions1, expected_actions2))) + + # # Test expected sequence of play. + # for expected_result, result in zip( + # zip(expected_actions1, expected_actions2), + # match.result): + # self.assertEqual() + # # player1.play(player2) + # # self.assertEqual(player1.history[i], outcome1) + # # self.assertEqual(player2.history[i], outcome2) + # self.assertEqual(match.result[i], outcome1) + # self.assertEqual(player2.history[i], outcome2) + def test_versus_with_incorrect_history_lengths(self): """Test the error raised by versus_test if expected actions do not @@ -695,8 +699,7 @@ def test_memory(player, opponent, memory_length, seed=0, turns=10): only the given amount of memory is used. """ # Play the match normally. - axl.seed(seed) - match = axl.Match((player, opponent), turns=turns) + match = axl.Match((player, opponent), turns=turns, seed=seed) plays = [p[0] for p in match.play()] # Play with limited history. @@ -705,7 +708,7 @@ def test_memory(player, opponent, memory_length, seed=0, turns=10): player._history = axl.LimitedHistory(memory_length) opponent._history = axl.LimitedHistory(memory_length) axl.seed(seed) - match = axl.Match((player, opponent), turns=turns, reset=False) + match = axl.Match((player, opponent), turns=turns, reset=False, seed=seed) limited_plays = [p[0] for p in match.play()] return plays == limited_plays diff --git a/axelrod/tests/unit/test_match.py b/axelrod/tests/unit/test_match.py index 71913d103..812fd79f3 100644 --- a/axelrod/tests/unit/test_match.py +++ b/axelrod/tests/unit/test_match.py @@ -4,6 +4,7 @@ import axelrod as axl from axelrod.deterministic_cache import DeterministicCache +from axelrod.random_ import RandomGenerator from axelrod.tests.property import games from hypothesis import example, given @@ -80,17 +81,24 @@ def test_example_prob_end(self): Test that matches have diff length and also that cache has recorded the outcomes """ +<<<<<<< HEAD p1, p2 = axl.Cooperator(), axl.Cooperator() match = axl.Match((p1, p2), prob_end=0.5) expected_lengths = [3, 1, 5] for seed, expected_length in zip(range(3), expected_lengths): axl.seed(seed) +======= + p1, p2 = axelrod.Cooperator(), axelrod.Cooperator() + expected_lengths = [2, 1, 1] + for seed, expected_length in zip(range(3), expected_lengths): + match = axelrod.Match((p1, p2), prob_end=0.5, seed=seed) +>>>>>>> First pass on reproducible matches and parallel tournaments with random seeding self.assertEqual(match.players[0].match_attributes["length"], float("inf")) self.assertEqual(len(match.play()), expected_length) self.assertEqual(match.noise, 0) self.assertEqual(match.game.RPST(), (3, 1, 0, 5)) self.assertEqual(len(match._cache), 1) - self.assertEqual(match._cache[(p1, p2)], [(C, C)] * 5) + self.assertEqual(match._cache[(p1, p2)], [(C, C)]) @given(turns=integers(min_value=1, max_value=200), game=games()) @example(turns=5, game=axl.DefaultGame) @@ -362,11 +370,12 @@ def test_sparklines(self): class TestSampleLength(unittest.TestCase): def test_sample_length(self): for seed, prob_end, expected_length in [ - (0, 0.5, 3), + (0, 0.5, 2), (1, 0.5, 1), - (2, 0.6, 4), - (3, 0.4, 1), + (2, 0.6, 1), + (3, 0.4, 2), ]: +<<<<<<< HEAD axl.seed(seed) self.assertEqual(axl.match.sample_length(prob_end), expected_length) @@ -375,3 +384,14 @@ def test_sample_with_0_prob(self): def test_sample_with_1_prob(self): self.assertEqual(axl.match.sample_length(1), 1) +======= + rg = RandomGenerator(seed) + r = rg.random() + self.assertEqual(axelrod.match.sample_length(prob_end, r), expected_length) + + def test_sample_with_0_prob(self): + self.assertEqual(axelrod.match.sample_length(0, 0.4), float("inf")) + + def test_sample_with_1_prob(self): + self.assertEqual(axelrod.match.sample_length(1, 0.6), 1) +>>>>>>> First pass on reproducible matches and parallel tournaments with random seeding diff --git a/axelrod/tests/unit/test_random_.py b/axelrod/tests/unit/test_random_.py index fdb1d361f..2e7a7560a 100644 --- a/axelrod/tests/unit/test_random_.py +++ b/axelrod/tests/unit/test_random_.py @@ -1,20 +1,28 @@ """Tests for the random functions.""" +<<<<<<< HEAD +======= +>>>>>>> First pass on reproducible matches and parallel tournaments with random seeding import unittest import random from collections import Counter +<<<<<<< HEAD import numpy +======= +from axelrod import Action, BulkRandomGenerator, Pdf, RandomGenerator +>>>>>>> First pass on reproducible matches and parallel tournaments with random seeding import axelrod as axl C, D = axl.Action.C, axl.Action.D -class TestRandom_(unittest.TestCase): +class TestRandomGenerator(unittest.TestCase): def test_return_values(self): +<<<<<<< HEAD self.assertEqual(axl.random_choice(1), C) self.assertEqual(axl.random_choice(0), D) axl.seed(1) @@ -34,11 +42,22 @@ def test_set_seed(self): self.assertEqual(numpy_random_numbers[0], numpy_random_numbers[1]) self.assertEqual(stdlib_random_numbers[0], stdlib_random_numbers[1]) +======= + random = RandomGenerator() + self.assertEqual(random.random_choice(1), C) + self.assertEqual(random.random_choice(0), D) + random.seed(1) + self.assertEqual(random.random_choice(), C) + random.seed(2) + self.assertEqual(random.random_choice(), D) +>>>>>>> First pass on reproducible matches and parallel tournaments with random seeding def test_seed_not_offset_by_deterministic_call(self): """Test that when called with p = 0 or 1, the random seed is not affected.""" + random = RandomGenerator() for p in [0, 1]: +<<<<<<< HEAD axl.seed(0) r = random.random() axl.seed(0) @@ -52,6 +71,48 @@ def test_random_flip(self): self.assertEqual(C, axl.random_flip(C, 0.2)) axl.seed(1) self.assertEqual(C, axl.random_flip(D, 0.2)) +======= + random.seed(0) + r = random.random() + random.seed(0) + random.random_choice(p) + self.assertEqual(r, random.random()) + + def test_random_flip(self): + random = RandomGenerator() + self.assertEqual(C, random.random_flip(C, 0)) + self.assertEqual(C, random.random_flip(D, 1)) + random.seed(0) + self.assertEqual(C, random.random_flip(C, 0.1)) + random.seed(1) + self.assertEqual(C, random.random_flip(D, 0.8)) + + +class TestBulkRandomGenerator(unittest.TestCase): + def test_generator(self): + """Test that the generator produces arrays of random values of + the expected length and that seeding works properly.""" + batch_size = 100 + batches = 20 + + # Test that we get the same results for two instances when the + # seeds are equal + rg1 = BulkRandomGenerator(seed=0, batch_size=batch_size) + randoms1 = [next(rg1) for _ in range(batches * batch_size)] + self.assertEqual(batches * batch_size, len(randoms1)) + + rg2 = BulkRandomGenerator(seed=0, batch_size=batch_size) + randoms2 = [next(rg2) for _ in range(batches * batch_size)] + self.assertEqual(batches * batch_size, len(randoms2)) + + self.assertSequenceEqual(randoms1, randoms2) + + # Test that we get different results for different seeds + rg3 = BulkRandomGenerator(seed=50, batch_size=batch_size) + randoms3 = [next(rg3) for _ in range(batches * batch_size)] + self.assertEqual(len(randoms3), len(randoms2)) + self.assertNotIn(randoms3[-1], randoms2) +>>>>>>> First pass on reproducible matches and parallel tournaments with random seeding class TestPdf(unittest.TestCase): @@ -70,8 +131,13 @@ def test_init(self): def test_sample(self): """Test that sample maps to correct domain""" all_samples = [] +<<<<<<< HEAD axl.seed(0) +======= + random = RandomGenerator() + random.seed(0) +>>>>>>> First pass on reproducible matches and parallel tournaments with random seeding for sample in range(100): all_samples.append(self.pdf.sample()) @@ -82,7 +148,14 @@ def test_seed(self): """Test that numpy seeds the sample properly""" for s in range(10): +<<<<<<< HEAD axl.seed(s) sample = self.pdf.sample() axl.seed(s) self.assertEqual(sample, self.pdf.sample()) +======= + pdf1 = Pdf(self.counter, s) + sample = pdf1.sample() + pdf2 = Pdf(self.counter, s) + self.assertEqual(sample, pdf2.sample()) +>>>>>>> First pass on reproducible matches and parallel tournaments with random seeding diff --git a/axelrod/tournament.py b/axelrod/tournament.py index 26144757c..2c8a95283 100644 --- a/axelrod/tournament.py +++ b/axelrod/tournament.py @@ -422,11 +422,12 @@ def _play_matches(self, chunk, build_results=True): (0, 1) -> [(C, D), (D, C),...] """ interactions = defaultdict(list) - index_pair, match_params, repetitions = chunk + index_pair, match_params, repetitions, seed = chunk p1_index, p2_index = index_pair player1 = self.players[p1_index].clone() player2 = self.players[p2_index].clone() match_params["players"] = (player1, player2) + match_params["seed"] = seed match = Match(**match_params) for _ in range(repetitions): match.play() From a518d0958df96ec90213bd403258cb62f636ccf4 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Thu, 27 Feb 2020 23:14:57 -0800 Subject: [PATCH 002/121] Fix many tests and errors for seed setting and propagation --- axelrod/__init__.py | 10 ++- axelrod/match.py | 18 +++-- axelrod/moran.py | 3 +- axelrod/player.py | 33 +++++++-- axelrod/random_.py | 3 + axelrod/strategies/hmm.py | 12 ++-- axelrod/strategies/memoryone.py | 5 +- axelrod/strategies/memorytwo.py | 5 +- axelrod/strategies/meta.py | 6 +- axelrod/strategies/oncebitten.py | 4 +- axelrod/strategies/punisher.py | 2 - axelrod/strategies/revised_downing.py | 1 + axelrod/strategies/titfortat.py | 5 +- .../tests/strategies/test_evolvable_player.py | 11 +-- axelrod/tests/strategies/test_player.py | 5 +- axelrod/tests/unit/test_moran.py | 72 +++++++------------ axelrod/tests/unit/test_pickling.py | 9 +-- axelrod/tournament.py | 2 +- 18 files changed, 114 insertions(+), 92 deletions(-) diff --git a/axelrod/__init__.py b/axelrod/__init__.py index 8c135be92..657f38ae5 100644 --- a/axelrod/__init__.py +++ b/axelrod/__init__.py @@ -2,9 +2,17 @@ # 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 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 +_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 Pdf, RandomGenerator, BulkRandomGenerator from axelrod.plot import Plot from axelrod.game import DefaultGame, Game diff --git a/axelrod/match.py b/axelrod/match.py index 3ba6e98fa..4074f090d 100644 --- a/axelrod/match.py +++ b/axelrod/match.py @@ -69,7 +69,6 @@ def __init__( self.noise = noise self.seed = seed - self._random = RandomGenerator(seed=self.seed) if game is None: self.game = Game() @@ -142,8 +141,8 @@ def simultaneous_play(self, player, coplayer, noise=0): # Note this uses the Match classes random generator, not either # player's random generator. A player shouldn't be able to # predict the outcome of this noise flip. - s1 = self.random_flip(s1, noise) - s2 = self.random_flip(s2, noise) + s1 = self._random.random_flip(s1, noise) + s2 = self._random.random_flip(s2, noise) player.update_history(s1, s2) coplayer.update_history(s2, s1) return s1, s2 @@ -166,9 +165,13 @@ def play(self): i.e. One entry per turn containing a pair of actions. """ + # if self._stochastic: self._random = RandomGenerator(seed=self.seed) - r = self._random.random() - turns = min(sample_length(self.prob_end, r), self.turns) + if self.prob_end: + r = self._random.random() + turns = min(sample_length(self.prob_end, r), self.turns) + else: + turns = self.turns cache_key = (self.players[0], self.players[1]) if self._stochastic or not self._cached_enough_turns(cache_key, turns): @@ -176,8 +179,9 @@ def play(self): if self.reset: p.reset() p.set_match_attributes(**self.match_attributes) - # Generate a random seed for each player - p.set_seed(self._random.randint(0, 100000000)) + # if p.classifier["stochastic"]: + # Generate a random seed for the player + p.set_seed(self._random.random_seed_int()) result = [] for _ in range(turns): plays = self.simultaneous_play( diff --git a/axelrod/moran.py b/axelrod/moran.py index 44c11a214..caf733f7e 100644 --- a/axelrod/moran.py +++ b/axelrod/moran.py @@ -29,7 +29,7 @@ def __init__( fitness_transformation: Callable = None, mutation_method="transition", stop_on_fixation=True, - seed = None + seed=None ) -> None: """ An agent based Moran process class. In each round, each player plays a @@ -362,6 +362,7 @@ def score_all(self) -> List: noise=self.noise, game=self.game, deterministic_cache=self.deterministic_cache, + seed=self._random.random_seed_int() ) match.play() match_scores = match.final_score_per_turn() diff --git a/axelrod/player.py b/axelrod/player.py index d076612ae..bf060cadf 100644 --- a/axelrod/player.py +++ b/axelrod/player.py @@ -6,6 +6,7 @@ import numpy as np +from axelrod import _module_random from axelrod.action import Action from axelrod.game import DefaultGame from axelrod.history import History @@ -27,6 +28,18 @@ def __new__(cls, *args, **kwargs): """Caches arguments for Player cloning.""" obj = super().__new__(cls) obj.init_kwargs = cls.init_params(*args, **kwargs) + # # Set up random seed from the module level random seed + # # in case the user doesn't specific one later. + # need_seed = False + # try: + # seed = kwargs["seed"] + # if seed is None: + # need_seed = True + # except KeyError: + # need_seed = True + # if need_seed: + # seed = _module_random.randint(0, 2**32-1) + # obj._seed = seed return obj @classmethod @@ -34,7 +47,7 @@ def init_params(cls, *args, **kwargs): """ Return a dictionary containing the init parameters of a strategy (without 'self'). - Use *args and *kwargs as value if specified + Use *args and **kwargs as value if specified and complete the rest with the default values. """ sig = inspect.signature(cls.__init__) @@ -53,7 +66,7 @@ def __init__(self): self._history = History() self.classifier = copy.deepcopy(self.classifier) self.set_match_attributes() - self.set_seed() + # self.set_seed(seed=self._seed) def __eq__(self, other): """ @@ -67,8 +80,8 @@ def __eq__(self, other): value = getattr(self, attribute, None) other_value = getattr(other, attribute, None) - if attribute == "_random": - # Don't compare the random seeds. + if attribute in ["_random", "_seed"]: + # Don't compare the random generators. continue if isinstance(value, np.ndarray): @@ -118,9 +131,13 @@ def set_match_attributes(self, length=-1, game=None, noise=0): self.receive_match_attributes() def set_seed(self, seed=None): - """Set a random seed for the player's random number - generator.""" - self._random = RandomGenerator(seed=seed) + """Set a random seed for the player's random number generator.""" + if seed is None: + # Warning: using global seed + self._seed = _module_random.random_seed_int() + else: + self._seed = seed + self._random = RandomGenerator(seed=self._seed) def __repr__(self): """The string method for the strategy. @@ -161,6 +178,7 @@ def clone(self): cls = self.__class__ new_player = cls(**self.init_kwargs) new_player.match_attributes = copy.copy(self.match_attributes) + # new_player.set_seed(self._seed) return new_player def reset(self): @@ -172,6 +190,7 @@ def reset(self): """ # This also resets the history. self.__init__(**self.init_kwargs) + # self.set_seed(self._seed) def update_history(self, play, coplay): self.history.append(play, coplay) diff --git a/axelrod/random_.py b/axelrod/random_.py index dcf081da6..14c56e2d5 100644 --- a/axelrod/random_.py +++ b/axelrod/random_.py @@ -27,6 +27,9 @@ def random(self, *args, **kwargs): def randint(self, *args, **kwargs): return self._random.randint(*args, **kwargs) + def random_seed_int(self): + return self.randint(0, 2**32-1) + def choice(self, *args, **kwargs): return self._random.choice(*args, **kwargs) diff --git a/axelrod/strategies/hmm.py b/axelrod/strategies/hmm.py index 09f384d73..9a76e9360 100644 --- a/axelrod/strategies/hmm.py +++ b/axelrod/strategies/hmm.py @@ -58,8 +58,7 @@ class SimpleHMM(object): """ def __init__( - self, transitions_C, transitions_D, emission_probabilities, initial_state, - seed=None + self, transitions_C, transitions_D, emission_probabilities, initial_state ) -> None: """ Params @@ -73,7 +72,6 @@ def __init__( self.transitions_D = transitions_D self.emission_probabilities = emission_probabilities self.state = initial_state - self._random = RandomGenerator(seed=seed) def is_well_formed(self) -> bool: """ @@ -161,8 +159,7 @@ def __init__( self.initial_state = initial_state self.initial_action = initial_action self.hmm = SimpleHMM( - copy_lists(transitions_C), copy_lists(transitions_D), list(emission_probabilities), initial_state, - seed=self._random.randint(0, 10000000) + copy_lists(transitions_C), copy_lists(transitions_D), list(emission_probabilities), initial_state ) assert self.hmm.is_well_formed() self.state = self.hmm.state @@ -190,6 +187,11 @@ def strategy(self, opponent: Player) -> Action: self.state = self.hmm.state return action + def set_seed(self, seed=None): + super().set_seed(seed=seed) + # Share RNG with HMM + self.hmm._random = self._random + class EvolvableHMMPlayer(HMMPlayer, EvolvablePlayer): """Evolvable version of HMMPlayer.""" diff --git a/axelrod/strategies/memoryone.py b/axelrod/strategies/memoryone.py index 90c0ad2e1..6bcfff21c 100644 --- a/axelrod/strategies/memoryone.py +++ b/axelrod/strategies/memoryone.py @@ -91,7 +91,10 @@ def strategy(self, opponent: Player) -> Action: # Determine which probability to use p = self._four_vector[(self.history[-1], opponent.history[-1])] # Draw a random number in [0, 1] to decide - return self._random.random_choice(p) + try: + return self._random.random_choice(p) + except AttributeError: + return D if p == 0 else C class WinStayLoseShift(MemoryOnePlayer): diff --git a/axelrod/strategies/memorytwo.py b/axelrod/strategies/memorytwo.py index 8bb050416..0709987e3 100644 --- a/axelrod/strategies/memorytwo.py +++ b/axelrod/strategies/memorytwo.py @@ -102,7 +102,10 @@ def strategy(self, opponent: Player) -> Action: (tuple(self.history[-2:]), tuple(opponent.history[-2:])) ] # Draw a random number in [0, 1] to decide - return self._random.random_choice(p) + try: + return self._random.random_choice(p) + except AttributeError: + return D if p == 0 else C class AON2(MemoryTwoPlayer): diff --git a/axelrod/strategies/meta.py b/axelrod/strategies/meta.py index 442a4dc39..98c585b85 100644 --- a/axelrod/strategies/meta.py +++ b/axelrod/strategies/meta.py @@ -58,7 +58,6 @@ def __init__(self, team=None): self.team = [t() for t in self.team] super().__init__() - self.set_seed() # This player inherits the classifiers of its team. # Note that memory_depth is not simply the max memory_depth of the team. @@ -76,9 +75,10 @@ def __init__(self, team=None): self._last_results = None def set_seed(self, seed=None): - super().set_seed(seed) + super().set_seed(seed=seed) + # Seed the team as well for t in self.team: - t.set_seed(self._random.randint(0, 100000000)) + t.set_seed(self._random.random_seed_int()) def receive_match_attributes(self): for t in self.team: diff --git a/axelrod/strategies/oncebitten.py b/axelrod/strategies/oncebitten.py index 4703e3c70..7113ccf1d 100644 --- a/axelrod/strategies/oncebitten.py +++ b/axelrod/strategies/oncebitten.py @@ -1,5 +1,3 @@ -import random - from axelrod.action import Action from axelrod.player import Player @@ -118,7 +116,7 @@ def __init__(self, forget_probability: float = 0.05) -> None: self.forget_probability = forget_probability def strategy(self, opponent: Player) -> Action: - r = random.random() + r = self._random.random() if not opponent.history: return self._initial if opponent.history[-1] == D: diff --git a/axelrod/strategies/punisher.py b/axelrod/strategies/punisher.py index dd8145576..43db0d74c 100644 --- a/axelrod/strategies/punisher.py +++ b/axelrod/strategies/punisher.py @@ -1,5 +1,3 @@ -from typing import List - from axelrod.action import Action from axelrod.player import Player diff --git a/axelrod/strategies/revised_downing.py b/axelrod/strategies/revised_downing.py index 530905c1b..21dc3c2d7 100644 --- a/axelrod/strategies/revised_downing.py +++ b/axelrod/strategies/revised_downing.py @@ -7,6 +7,7 @@ C, D = Action.C, Action.D + class RevisedDowning(Player): """ Strategy submitted to Axelrod's second tournament by Leslie Downing. diff --git a/axelrod/strategies/titfortat.py b/axelrod/strategies/titfortat.py index b91b80018..e8cde887c 100644 --- a/axelrod/strategies/titfortat.py +++ b/axelrod/strategies/titfortat.py @@ -909,7 +909,10 @@ def strategy(self, opponent: Player) -> Action: if self.act_random: self.act_random = False - return self._random.random_choice(self.p) + try: + return self._random.random_choice(self.p) + except AttributeError: + return D if self.p == 0 else C self.act_random = True return opponent.history[-1] diff --git a/axelrod/tests/strategies/test_evolvable_player.py b/axelrod/tests/strategies/test_evolvable_player.py index ccb6ac37d..06ff85ea2 100644 --- a/axelrod/tests/strategies/test_evolvable_player.py +++ b/axelrod/tests/strategies/test_evolvable_player.py @@ -10,8 +10,11 @@ C, D = Action.C, Action.D -def PartialClass(cls, **kwargs): +def seed(x): + return + +def PartialClass(cls, **kwargs): class PartialedClass(cls): __init__ = functools.partialmethod( cls.__init__, **kwargs) @@ -153,14 +156,12 @@ def test_serialization_csv(self): def behavior_test(self, player1, player2): """Test that the evolvable player plays the same as its (nonevolvable) parent class.""" for opponent_class in [axl.Random, axl.TitForTat, axl.Alternator]: - axl.seed(0) opponent = opponent_class() - match = axl.Match((player1.clone(), opponent)) + match = axl.Match((player1.clone(), opponent), seed=7) results1 = match.play() - axl.seed(0) opponent = opponent_class() - match = axl.Match((player2.clone(), opponent)) + match = axl.Match((player2.clone(), opponent), seed=7) results2 = match.play() self.assertEqual(results1, results2) diff --git a/axelrod/tests/strategies/test_player.py b/axelrod/tests/strategies/test_player.py index 667924123..9e690d3d6 100644 --- a/axelrod/tests/strategies/test_player.py +++ b/axelrod/tests/strategies/test_player.py @@ -413,8 +413,7 @@ def equality_of_players_test(self, p1, p2, seed, opponent): a2 = opponent() self.assertEqual(p1, p2) for player, op in [(p1, a1), (p2, a2)]: - m = axl.Match(players=(player, op), reset=True, seed=seed, - turns=10) + m = axelrod.Match(players=(player, op), turns=10, seed=seed) m.play() self.assertEqual(p1, p2) p1 = pickle.loads(pickle.dumps(p1)) @@ -490,7 +489,7 @@ def test_clone(self, seed): self.assertEqual(player2.classifier, player1.classifier) self.assertEqual(player2.match_attributes, player1.match_attributes) - turns = 20 + turns = 5 for op in [ axl.Cooperator(), axl.Defector(), diff --git a/axelrod/tests/unit/test_moran.py b/axelrod/tests/unit/test_moran.py index d972b288f..c9b413842 100644 --- a/axelrod/tests/unit/test_moran.py +++ b/axelrod/tests/unit/test_moran.py @@ -3,9 +3,8 @@ import random from collections import Counter import matplotlib.pyplot as plt - import axelrod as axl -from axelrod.moran import fitness_proportionate_selection +from axelrod import MoranProcess from axelrod.tests.property import strategy_lists from hypothesis import example, given, settings @@ -133,11 +132,11 @@ def test_matchup_indices(self): mp = axl.MoranProcess(players, mode="bd", interaction_graph=graph) self.assertEqual(mp._matchup_indices(), {(0, 1), (1, 2), (2, 0)}) - def test_fps(self): - self.assertEqual(fitness_proportionate_selection([0, 0, 1]), 2) - axl.seed(1) - self.assertEqual(fitness_proportionate_selection([1, 1, 1]), 0) - self.assertEqual(fitness_proportionate_selection([1, 1, 1]), 2) + # def test_fps(self): + # self.assertEqual(fitness_proportionate_selection([0, 0, 1]), 2) + # axelrod.seed(1) + # self.assertEqual(fitness_proportionate_selection([1, 1, 1]), 0) + # self.assertEqual(fitness_proportionate_selection([1, 1, 1]), 2) def test_exit_condition(self): p1, p2 = axl.Cooperator(), axl.Cooperator() @@ -147,8 +146,7 @@ def test_exit_condition(self): def test_two_players(self): p1, p2 = axl.Cooperator(), axl.Defector() - axl.seed(17) - mp = axl.MoranProcess((p1, p2)) + mp = MoranProcess((p1, p2), seed=99) populations = mp.play() self.assertEqual(len(mp), 5) self.assertEqual(len(populations), 5) @@ -157,8 +155,7 @@ def test_two_players(self): def test_two_prob_end(self): p1, p2 = axl.Random(), axl.TitForTat() - axl.seed(0) - mp = axl.MoranProcess((p1, p2), prob_end=0.5) + mp = MoranProcess((p1, p2), prob_end=0.5, seed=10) populations = mp.play() self.assertEqual(len(mp), 4) self.assertEqual(len(populations), 4) @@ -168,9 +165,8 @@ def test_two_prob_end(self): def test_different_game(self): # Possible for Cooperator to become fixed when using a different game p1, p2 = axl.Cooperator(), axl.Defector() - axl.seed(0) game = axl.Game(r=4, p=2, s=1, t=6) - mp = axl.MoranProcess((p1, p2), turns=5, game=game) + mp = MoranProcess((p1, p2), turns=5, game=game, seed=88) populations = mp.play() self.assertEqual(mp.winning_strategy_name, str(p1)) @@ -179,8 +175,7 @@ def test_death_birth(self): p1, p2 = axl.Cooperator(), axl.Defector() seeds = range(0, 20) for seed in seeds: - axl.seed(seed) - mp = axl.MoranProcess((p1, p2), mode="db") + mp = MoranProcess((p1, p2), mode="db", seed=seed) mp.play() self.assertIsNotNone(mp.winning_strategy_name) # Number of populations is 2: the original and the one after the first round. @@ -196,20 +191,17 @@ def test_death_birth_outcomes(self): players.append(axl.Cooperator()) players.append(axl.Defector()) for seed, outcome in seeds: - axl.seed(seed) - mp = axl.MoranProcess(players, mode="bd") + mp = MoranProcess(players, mode="bd", seed=seed) mp.play() winner = mp.winning_strategy_name - axl.seed(seed) - mp = axl.MoranProcess(players, mode="db") + mp = MoranProcess(players, mode="db", seed=seed) mp.play() winner2 = mp.winning_strategy_name self.assertEqual((winner == winner2), outcome) def test_two_random_players(self): p1, p2 = axl.Random(p=0.5), axl.Random(p=0.25) - axl.seed(0) - mp = axl.MoranProcess((p1, p2)) + mp = MoranProcess((p1, p2), seed=66) populations = mp.play() self.assertEqual(len(mp), 2) self.assertEqual(len(populations), 2) @@ -218,8 +210,7 @@ def test_two_random_players(self): def test_two_players_with_mutation(self): p1, p2 = axl.Cooperator(), axl.Defector() - axl.seed(5) - mp = axl.MoranProcess((p1, p2), mutation_rate=0.2, stop_on_fixation=False) + mp = MoranProcess((p1, p2), mutation_rate=0.2, stop_on_fixation=False, seed=5) self.assertDictEqual(mp.mutation_targets, {str(p1): [p2], str(p2): [p1]}) # Test that mutation causes the population to alternate between # fixations @@ -244,8 +235,7 @@ def test_play_exception(self): def test_three_players(self): players = [axl.Cooperator(), axl.Cooperator(), axl.Defector()] - axl.seed(11) - mp = axl.MoranProcess(players) + mp = MoranProcess(players, seed=11) populations = mp.play() self.assertEqual(len(mp), 7) self.assertEqual(len(populations), 7) @@ -363,7 +353,6 @@ def test_population_plot(self): self.assertEqual(ax.get_ylim(), (0, 5.25)) def test_cooperator_can_win_with_fitness_transformation(self): - axl.seed(689) players = ( axl.Cooperator(), axl.Defector(), @@ -372,17 +361,17 @@ def test_cooperator_can_win_with_fitness_transformation(self): ) w = 0.95 fitness_transformation = lambda score: 1 - w + w * score - mp = axl.MoranProcess( - players, turns=10, fitness_transformation=fitness_transformation + mp = MoranProcess( + players, turns=10, fitness_transformation=fitness_transformation, + seed=689 ) populations = mp.play() self.assertEqual(mp.winning_strategy_name, "Cooperator") def test_atomic_mutation_fsm(self): - axl.seed(12) players = [axl.EvolvableFSMPlayer(num_states=2, initial_state=1, initial_action=C) for _ in range(5)] - mp = axl.MoranProcess(players, turns=10, mutation_method="atomic") + mp = MoranProcess(players, turns=10, mutation_method="atomic", seed=12) population = mp.play() self.assertEqual( mp.winning_strategy_name, @@ -391,29 +380,26 @@ def test_atomic_mutation_fsm(self): self.assertTrue(mp.fixated) def test_atomic_mutation_cycler(self): - axl.seed(10) cycle_length = 5 players = [axl.EvolvableCycler(cycle_length=cycle_length) for _ in range(5)] - mp = axl.MoranProcess(players, turns=10, mutation_method="atomic") + mp = MoranProcess(players, turns=10, mutation_method="atomic", seed=10) population = mp.play() self.assertEqual(mp.winning_strategy_name, 'EvolvableCycler: CDCDD, 5, 0.2, 1') self.assertEqual(len(mp.populations), 19) self.assertTrue(mp.fixated) def test_mutation_method_exceptions(self): - axl.seed(10) + cycle_length = 5 players = [axl.EvolvableCycler(cycle_length=cycle_length) for _ in range(5)] with self.assertRaises(ValueError): - axl.MoranProcess(players, turns=10, mutation_method="random") + MoranProcess(players, turns=10, mutation_method="random", seed=10) - axl.seed(0) players = [axl.Cycler(cycle="CD" * random.randint(2, 10)) for _ in range(10)] - - mp = axl.MoranProcess(players, turns=10, mutation_method="atomic") + mp = MoranProcess(players, turns=10, mutation_method="atomic", seed=53) with self.assertRaises(TypeError): for _ in range(10): next(mp) @@ -431,12 +417,10 @@ def test_complete(self): players.append(axl.Cooperator()) players.append(axl.Defector()) for seed in seeds: - axl.seed(seed) - mp = axl.MoranProcess(players) + mp = MoranProcess(players, seed=seed) mp.play() winner = mp.winning_strategy_name - axl.seed(seed) - mp = axl.MoranProcess(players, interaction_graph=graph) + mp = MoranProcess(players, interaction_graph=graph, seed=seed) mp.play() winner2 = mp.winning_strategy_name self.assertEqual(winner, winner2) @@ -453,12 +437,10 @@ def test_cycle(self): for _ in range(N // 2): players.append(axl.Defector()) for seed, outcome in seeds: - axl.seed(seed) - mp = axl.MoranProcess(players) + mp = MoranProcess(players, seed=seed) mp.play() winner = mp.winning_strategy_name - axl.seed(seed) - mp = axl.MoranProcess(players, interaction_graph=graph) + mp = MoranProcess(players, interaction_graph=graph, seed=seed) mp.play() winner2 = mp.winning_strategy_name self.assertEqual((winner == winner2), outcome) diff --git a/axelrod/tests/unit/test_pickling.py b/axelrod/tests/unit/test_pickling.py index 1cb14101d..da2a9fc1f 100644 --- a/axelrod/tests/unit/test_pickling.py +++ b/axelrod/tests/unit/test_pickling.py @@ -218,12 +218,10 @@ def assert_original_equals_pickled(self, player_, turns=10): opponent_1 = opponent_class() opponent_2 = opponent_class() - axl.seed(0) - match_1 = axl.Match((player, opponent_1), turns=turns) + match_1 = axl.Match((player, opponent_1), turns=turns, seed=1) result_1 = match_1.play() - axl.seed(0) - match_2 = axl.Match((clone, opponent_2), turns=turns) + match_2 = axl.Match((clone, opponent_2), turns=turns, seed=1) result_2 = match_2.play() self.assertEqual(result_1, result_2) @@ -243,10 +241,9 @@ def test_sequence_player(self): self.assert_equals_instance_from_pickling(player) opponents = (axl.Defector, axl.Cooperator, axl.Random, axl.CyclerCCCDCD) for opponent_class in opponents: - axl.seed(10) player.reset() opponent = opponent_class() - match_1 = axl.Match((player, opponent), turns=20) + match_1 = axl.Match((player, opponent), turns=20, seed=10) _ = match_1.play() self.assert_equals_instance_from_pickling(player) diff --git a/axelrod/tournament.py b/axelrod/tournament.py index 2c8a95283..6b96edbcc 100644 --- a/axelrod/tournament.py +++ b/axelrod/tournament.py @@ -10,7 +10,7 @@ import axelrod.interaction_utils as iu import tqdm from axelrod import DEFAULT_TURNS -from axelrod.action import Action, actions_to_str, str_to_actions +from axelrod.action import Action, actions_to_str from axelrod.player import Player from .game import Game From 96cef679c8f3aba8bf5f6e29915e489496a80ad4 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sun, 1 Mar 2020 20:22:32 -0800 Subject: [PATCH 003/121] Rewrite many tests to use new seeding method. Rewrite Evolveable player classes to use new seeding methods. --- axelrod/__init__.py | 6 +- axelrod/_strategy_utils.py | 8 +- axelrod/evolvable_player.py | 32 ++- axelrod/fingerprint.py | 5 +- axelrod/match.py | 4 +- axelrod/match_generator.py | 1 + axelrod/moran.py | 9 +- axelrod/random_.py | 21 +- axelrod/result_set.py | 2 +- axelrod/strategies/ann.py | 17 +- axelrod/strategies/cycler.py | 21 +- axelrod/strategies/finite_state_machines.py | 47 ++--- axelrod/strategies/gambler.py | 21 +- axelrod/strategies/hmm.py | 60 +++--- axelrod/strategies/lookerup.py | 39 ++-- axelrod/tests/integration/test_filtering.py | 5 +- axelrod/tests/integration/test_matches.py | 3 +- axelrod/tests/integration/test_tournament.py | 5 +- axelrod/tests/property.py | 9 +- axelrod/tests/strategies/test_ann.py | 5 +- axelrod/tests/strategies/test_cycler.py | 37 ++-- .../tests/strategies/test_evolvable_player.py | 69 +++---- .../strategies/test_finite_state_machines.py | 4 +- axelrod/tests/strategies/test_gambler.py | 5 +- axelrod/tests/strategies/test_geller.py | 16 +- axelrod/tests/strategies/test_hmm.py | 55 ++--- axelrod/tests/strategies/test_human.py | 3 + axelrod/tests/strategies/test_hunter.py | 25 +-- axelrod/tests/strategies/test_lookerup.py | 7 +- axelrod/tests/strategies/test_memorytwo.py | 15 +- axelrod/tests/strategies/test_meta.py | 18 +- axelrod/tests/strategies/test_mindreader.py | 6 +- axelrod/tests/strategies/test_oncebitten.py | 11 +- axelrod/tests/strategies/test_player.py | 43 ++-- axelrod/tests/strategies/test_stalker.py | 3 +- axelrod/tests/strategies/test_titfortat.py | 82 ++++---- axelrod/tests/unit/test_fingerprint.py | 18 +- axelrod/tests/unit/test_history.py | 4 +- axelrod/tests/unit/test_match.py | 43 +--- axelrod/tests/unit/test_match_generator.py | 49 ++++- axelrod/tests/unit/test_moran.py | 94 +++++---- axelrod/tests/unit/test_random_.py | 57 ------ axelrod/tests/unit/test_resultset.py | 6 +- .../tests/unit/test_strategy_transformers.py | 191 +++++++++--------- axelrod/tests/unit/test_strategy_utils.py | 19 +- axelrod/tests/unit/test_tournament.py | 98 +++++++-- axelrod/tournament.py | 2 + requirements.txt | 1 + 48 files changed, 652 insertions(+), 649 deletions(-) diff --git a/axelrod/__init__.py b/axelrod/__init__.py index 657f38ae5..4f5802d67 100644 --- a/axelrod/__init__.py +++ b/axelrod/__init__.py @@ -6,14 +6,14 @@ from axelrod.random_ import Pdf, RandomGenerator, BulkRandomGenerator # Initialize module level Random -# This is seeded by the clock / OS entropy pool +# 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 +# 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.random_ import Pdf, RandomGenerator, BulkRandomGenerator from axelrod.plot import Plot from axelrod.game import DefaultGame, Game from axelrod.history import History, LimitedHistory diff --git a/axelrod/_strategy_utils.py b/axelrod/_strategy_utils.py index d028a072b..6daca1c81 100644 --- a/axelrod/_strategy_utils.py +++ b/axelrod/_strategy_utils.py @@ -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 @@ -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. diff --git a/axelrod/evolvable_player.py b/axelrod/evolvable_player.py index e80da1c69..0f3e370eb 100644 --- a/axelrod/evolvable_player.py +++ b/axelrod/evolvable_player.py @@ -1,6 +1,6 @@ import base64 +import copy from pickle import dumps, loads -from random import randrange from typing import Dict, List from .player import Player @@ -21,6 +21,10 @@ class EvolvablePlayer(Player): parent_class = Player parent_kwargs = [] # type: List[str] + def __init__(self, seed=None): + 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(): @@ -32,6 +36,24 @@ def create_new(self, **kwargs): init_kwargs.update(kwargs) return self.__class__(**init_kwargs) + # def clone(self): + # """Clones the player without history, reapplying configuration + # parameters as necessary. + # + # We're overriding Player.clone to also propagate the seed because EvolvablePlayers + # currently randomize themselves on initialization. + # """ + # + # # You may be tempted to re-implement using the `copy` module + # # Note that this would require a deepcopy in some cases and there may + # # be significant changes required throughout the library. + # # Consider overriding in special cases only if necessary + # cls = self.__class__ + # new_player = cls(**self.init_kwargs, seed=self._seed) + # new_player.match_attributes = copy.copy(self.match_attributes) + # # new_player.set_seed(self._seed) + # return new_player + # Serialization and deserialization. You may overwrite to obtain more human readable serializations # but you must overwrite both. @@ -73,15 +95,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) diff --git a/axelrod/fingerprint.py b/axelrod/fingerprint.py index 20390abfd..3c970ddca 100644 --- a/axelrod/fingerprint.py +++ b/axelrod/fingerprint.py @@ -245,7 +245,6 @@ def _construct_tournament_elements( progress_bar : bool Whether or not to create a progress bar which will be updated - Returns ---------- edges : list of tuples @@ -279,6 +278,7 @@ def fingerprint( processes: int = None, filename: str = None, progress_bar: bool = True, + seed: int = None ) -> dict: """Build and play the spatial tournament. @@ -322,7 +322,8 @@ def fingerprint( self.step = step self.spatial_tournament = axl.Tournament( - tourn_players, turns=turns, repetitions=repetitions, edges=edges + tourn_players, turns=turns, repetitions=repetitions, edges=edges, + seed=seed ) self.spatial_tournament.play( build_results=False, diff --git a/axelrod/match.py b/axelrod/match.py index 4074f090d..4e7b5b4e4 100644 --- a/axelrod/match.py +++ b/axelrod/match.py @@ -69,6 +69,7 @@ def __init__( self.noise = noise self.seed = seed + self._random = RandomGenerator(seed=self.seed) if game is None: self.game = Game() @@ -165,8 +166,6 @@ def play(self): i.e. One entry per turn containing a pair of actions. """ - # if self._stochastic: - self._random = RandomGenerator(seed=self.seed) if self.prob_end: r = self._random.random() turns = min(sample_length(self.prob_end, r), self.turns) @@ -181,6 +180,7 @@ def play(self): p.set_match_attributes(**self.match_attributes) # if p.classifier["stochastic"]: # Generate a random seed for the player + # TODO: Seeds only for stochastic players p.set_seed(self._random.random_seed_int()) result = [] for _ in range(turns): diff --git a/axelrod/match_generator.py b/axelrod/match_generator.py index dd0a49fb1..c0ad188bd 100644 --- a/axelrod/match_generator.py +++ b/axelrod/match_generator.py @@ -38,6 +38,7 @@ def __init__( Mapping attribute names to values which should be passed to players. The default is to use the correct values for turns, game and noise but these can be overridden if desired. + seed : int """ self.players = players self.turns = turns diff --git a/axelrod/moran.py b/axelrod/moran.py index caf733f7e..faca0701b 100644 --- a/axelrod/moran.py +++ b/axelrod/moran.py @@ -10,7 +10,7 @@ from .deterministic_cache import DeterministicCache from .graph import Graph, complete_graph from .match import Match -from .random_ import RandomGenerator +from .random_ import RandomGenerator, BulkRandomGenerator class MoranProcess(object): @@ -101,6 +101,7 @@ def __init__( self.mutation_rate = mutation_rate self.stop_on_fixation = stop_on_fixation self._random = RandomGenerator(seed=seed) + self._bulk_random = BulkRandomGenerator(self._random.random_seed_int()) m = mutation_method.lower() if m in ["atomic", "transition"]: self.mutation_method = m @@ -249,13 +250,13 @@ def birth(self, index: int = None) -> int: # possible choices scores.pop(index) # Make sure to get the correct index post-pop - j = fitness_proportionate_selection( + j = self.fitness_proportionate_selection( scores, fitness_transformation=self.fitness_transformation ) if j >= index: j += 1 else: - j = fitness_proportionate_selection( + j = self.fitness_proportionate_selection( scores, fitness_transformation=self.fitness_transformation ) return j @@ -362,7 +363,7 @@ def score_all(self) -> List: noise=self.noise, game=self.game, deterministic_cache=self.deterministic_cache, - seed=self._random.random_seed_int() + seed=next(self._bulk_random) ) match.play() match_scores = match.final_score_per_turn() diff --git a/axelrod/random_.py b/axelrod/random_.py index 14c56e2d5..b059803df 100644 --- a/axelrod/random_.py +++ b/axelrod/random_.py @@ -119,23 +119,26 @@ class BulkRandomGenerator(object): """Bulk generator of random integers for tournament seeding and reproducibility. Use like a generator.""" def __init__(self, seed=None, batch_size=1000): - self.random_generator = RandomState() - self.random_generator.seed(seed) + self._random_generator = RandomState() + self._random_generator.seed(seed) self._ints = None - self.batch_size = batch_size + self._batch_size = batch_size + self._index = 0 self._fill_ints() def _fill_ints(self): - ints = self.random_generator.randint( + ints = self._random_generator.randint( low=0, high=2**32 - 1, - size=self.batch_size) - self._ints = (x for x in ints) + size=self._batch_size) + self._ints = [x for x in ints] + self._index = 0 def __next__(self): try: - x = next(self._ints) + x = self._ints[self._index] + self._index += 1 return x - except StopIteration: + except IndexError: self._fill_ints() - return next(self._ints) + return self.__next__() diff --git a/axelrod/result_set.py b/axelrod/result_set.py index 36e33a4b7..bffbd283e 100644 --- a/axelrod/result_set.py +++ b/axelrod/result_set.py @@ -7,11 +7,11 @@ import numpy as np import tqdm -from axelrod.action import Action import dask as da import dask.dataframe as dd +from axelrod.action import Action from . import eigen C, D = Action.C, Action.D diff --git a/axelrod/strategies/ann.py b/axelrod/strategies/ann.py index 2d3a1bc85..e59dbe205 100644 --- a/axelrod/strategies/ann.py +++ b/axelrod/strategies/ann.py @@ -1,6 +1,5 @@ from typing import List, Tuple import numpy as np -import numpy.random as random from axelrod.action import Action from axelrod.load_data_ import load_weights from axelrod.evolvable_player import EvolvablePlayer, InsufficientParametersError, crossover_lists @@ -229,7 +228,9 @@ def __init__( weights: List[float] = None, mutation_probability: float = None, mutation_distance: int = 5, + seed: int = None ) -> None: + self.set_seed(seed=seed) num_features, num_hidden, weights, mutation_probability = self._normalize_parameters( num_features, num_hidden, weights, mutation_probability) ANN.__init__(self, @@ -245,25 +246,23 @@ def __init__( weights=weights, mutation_probability=mutation_probability) - @classmethod - def _normalize_parameters(cls, num_features=None, num_hidden=None, weights=None, mutation_probability=None): + def _normalize_parameters(self, num_features=None, num_hidden=None, weights=None, mutation_probability=None): if not (num_features and num_hidden): raise InsufficientParametersError("Insufficient Parameters to instantiate EvolvableANN") size = num_weights(num_features, num_hidden) if not weights: - weights = [random.uniform(-1, 1) for _ in range(size)] + weights = [self._random.uniform(-1, 1) for _ in range(size)] if mutation_probability is None: mutation_probability = 10. / size return num_features, num_hidden, weights, mutation_probability - @staticmethod - def mutate_weights(weights, num_features, num_hidden, mutation_probability, + def mutate_weights(self, weights, num_features, num_hidden, mutation_probability, mutation_distance): size = num_weights(num_features, num_hidden) - randoms = random.random(size) + randoms = self._random.random(size) for i, r in enumerate(randoms): if r < mutation_probability: - p = 1 + random.uniform(-1, 1) * mutation_distance + p = 1 + self._random.uniform(-1, 1) * mutation_distance weights[i] *= p return weights @@ -276,7 +275,7 @@ def mutate(self): def crossover(self, other): if other.__class__ != self.__class__: raise TypeError("Crossover must be between the same player classes.") - weights = crossover_lists(self.weights, other.weights) + weights = crossover_lists(self.weights, other.weights, self._random) return self.create_new(weights=weights) diff --git a/axelrod/strategies/cycler.py b/axelrod/strategies/cycler.py index 509141717..645108c7a 100644 --- a/axelrod/strategies/cycler.py +++ b/axelrod/strategies/cycler.py @@ -1,6 +1,5 @@ import copy import itertools -import random from typing import List, Tuple from axelrod.action import Action, actions_to_str, str_to_actions @@ -109,8 +108,10 @@ def __init__( cycle: str = None, cycle_length: int = None, mutation_probability: float = 0.2, - mutation_potency: int = 1 + mutation_potency: int = 1, + seed: int = None ) -> None: + self.set_seed(seed=seed) cycle, cycle_length = self._normalize_parameters(cycle, cycle_length) # The following __init__ sets self.cycle = cycle Cycler.__init__(self, cycle=cycle) @@ -122,31 +123,29 @@ def __init__( self.mutation_probability = mutation_probability self.mutation_potency = mutation_potency - @classmethod - def _normalize_parameters(cls, cycle=None, cycle_length=None) -> Tuple[str, int]: + def _normalize_parameters(self, cycle=None, cycle_length=None) -> Tuple[str, int]: """Compute other parameters from those that may be missing, to ensure proper cloning.""" if not cycle: if not cycle_length: raise InsufficientParametersError("Insufficient Parameters to instantiate EvolvableCycler") - cycle = cls._generate_random_cycle(cycle_length) + cycle = self._generate_random_cycle(cycle_length) cycle_length = len(cycle) return cycle, cycle_length - @classmethod - def _generate_random_cycle(cls, cycle_length: int) -> str: + def _generate_random_cycle(self, cycle_length: int) -> str: """ Generate a sequence of random moves """ - return actions_to_str(random.choice(actions) for _ in range(cycle_length)) + return actions_to_str(self._random.choice(actions) for _ in range(cycle_length)) def mutate(self) -> EvolvablePlayer: """ Basic mutation which may change any random actions in the sequence. """ - if random.random() <= self.mutation_probability: + if self._random.random() <= self.mutation_probability: mutated_sequence = list(str_to_actions(self.cycle)) for _ in range(self.mutation_potency): - index_to_change = random.randint(0, len(mutated_sequence) - 1) + index_to_change = self._random.randint(0, len(mutated_sequence) - 1) mutated_sequence[index_to_change] = mutated_sequence[index_to_change].flip() cycle = actions_to_str(mutated_sequence) else: @@ -160,7 +159,7 @@ def crossover(self, other) -> EvolvablePlayer: """ if other.__class__ != self.__class__: raise TypeError("Crossover must be between the same player classes.") - cycle_list = crossover_lists(self.cycle, other.cycle) + cycle_list = crossover_lists(self.cycle, other.cycle, self._random) cycle = "".join(cycle_list) cycle, _ = self._normalize_parameters(cycle) return self.create_new(cycle=cycle) diff --git a/axelrod/strategies/finite_state_machines.py b/axelrod/strategies/finite_state_machines.py index e7bdd7f63..a99cbdf89 100644 --- a/axelrod/strategies/finite_state_machines.py +++ b/axelrod/strategies/finite_state_machines.py @@ -1,8 +1,5 @@ import itertools -from random import randrange -from typing import Any, List, Sequence, Tuple, Union -import numpy.random as random -from numpy.random import choice +from typing import Any, Sequence, Tuple from axelrod.action import Action from axelrod.evolvable_player import EvolvablePlayer, InsufficientParametersError, copy_lists from axelrod.player import Player @@ -147,9 +144,11 @@ def __init__( initial_action: Action = None, num_states: int = None, mutation_probability: float = 0.1, + seed: int = None ) -> None: """If transitions, initial_state, and initial_action are None then generate random parameters using num_states.""" + self.set_seed(seed=seed) transitions, initial_state, initial_action, num_states = self._normalize_parameters( transitions, initial_state, initial_action, num_states) FSMPlayer.__init__( @@ -173,14 +172,13 @@ def normalize_transitions(cls, transitions: Sequence[Sequence]) -> Tuple[Tuple[A normalized.append(tuple(t)) return tuple(normalized) - @classmethod - def _normalize_parameters(cls, transitions: Tuple = None, initial_state: int = None, initial_action: Action = None, + def _normalize_parameters(self, transitions: Tuple = None, initial_state: int = None, initial_action: Action = None, num_states: int = None) -> Tuple[Tuple, int, Action, int]: if not ((transitions is not None) and (initial_state is not None) and (initial_action is not None)): if not num_states: raise InsufficientParametersError("Insufficient Parameters to instantiate EvolvableFSMPlayer") - transitions, initial_state, initial_action = cls.random_params(num_states) - transitions = cls.normalize_transitions(transitions) + transitions, initial_state, initial_action = self.random_params(num_states) + transitions = self.normalize_transitions(transitions) num_states = len(transitions) // 2 return transitions, initial_state, initial_action, num_states @@ -188,32 +186,30 @@ def _normalize_parameters(cls, transitions: Tuple = None, initial_state: int = N def num_states(self) -> int: return self.fsm.num_states() - @classmethod - def random_params(cls, num_states: int) -> Tuple[Tuple[Transition, ...], int, Action]: + def random_params(self, num_states: int) -> Tuple[Tuple[Transition, ...], int, Action]: rows = [] for j in range(num_states): for action in actions: - next_state = randrange(num_states) - next_action = choice(actions) + next_state = self._random.randint(num_states) + next_action = self._random.choice(actions) row = (j, action, next_state, next_action) rows.append(row) - initial_state = randrange(num_states) - initial_action = choice(actions) + initial_state = self._random.randint(0, num_states) + initial_action = self._random.choice(actions) return tuple(rows), initial_state, initial_action - @staticmethod - def mutate_rows(rows, mutation_probability): + def mutate_rows(self, rows, mutation_probability): rows = list(rows) - randoms = random.random(len(rows)) + randoms = self._random.random(len(rows)) # Flip each value with a probability proportional to the mutation rate for i, row in enumerate(rows): if randoms[i] < mutation_probability: row[3] = row[3].flip() # Swap Two Nodes? - if random.random() < 0.5: + if self._random.random() < 0.5: nodes = len(rows) // 2 - n1 = randrange(nodes) - n2 = randrange(nodes) + n1 = self._random.randint(0, nodes) + n2 = self._random.randint(0, nodes) for j, row in enumerate(rows): if row[0] == n1: row[0] = n2 @@ -224,11 +220,11 @@ def mutate_rows(rows, mutation_probability): def mutate(self): initial_action = self.initial_action - if random.random() < self.mutation_probability / 10: + if self._random.random() < self.mutation_probability / 10: initial_action = self.initial_action.flip() initial_state = self.initial_state - if random.random() < self.mutation_probability / (10 * self.num_states): - initial_state = randrange(self.num_states) + if self._random.random() < self.mutation_probability / (10 * self.num_states): + initial_state = self._random.randint(0, self.num_states) try: transitions = self.mutate_rows(self.fsm.transitions(), self.mutation_probability) self.fsm = SimpleFSM(transitions, self.initial_state) @@ -241,10 +237,9 @@ def mutate(self): initial_action=initial_action, ) - @staticmethod - def crossover_rows(rows1, rows2): + def crossover_rows(self, rows1, rows2): num_states = len(rows1) // 2 - cross_point = 2 * randrange(num_states) + cross_point = 2 * self._random.randint(0, num_states) new_rows = copy_lists(rows1[:cross_point]) new_rows += copy_lists(rows2[cross_point:]) return new_rows diff --git a/axelrod/strategies/gambler.py b/axelrod/strategies/gambler.py index 578386441..d2a06ec57 100644 --- a/axelrod/strategies/gambler.py +++ b/axelrod/strategies/gambler.py @@ -4,7 +4,6 @@ For the original see: https://gist.github.com/GDKO/60c3d0fd423598f3c4e4 """ -import random from typing import Any from axelrod.action import Action @@ -54,7 +53,8 @@ def __init__( initial_actions: tuple = None, pattern: Any = None, # pattern is str or tuple of Actions. parameters: Plays = None, - mutation_probability: float = None + mutation_probability: float = None, + seed: int = None ) -> None: EvolvableLookerUp.__init__( self, @@ -64,6 +64,7 @@ def __init__( parameters=parameters, mutation_probability=mutation_probability ) + self.set_seed(seed=seed) self.pattern = list(self.pattern) Gambler.__init__( self, @@ -83,17 +84,11 @@ def __init__( # The mutate and crossover methods are mostly inherited from EvolvableLookerUp, except for the following # modifications. - @classmethod - def random_value(cls): - # TODO: For reproducibility, this invocation of random may need to be folded into the - # evolutionary algorithm class, once it is merged into Axelrod. - return random.random() - - @classmethod - def mutate_value(cls, value): - # TODO: For reproducibility, this invocation of random may need to be folded into the - # evolutionary algorithm class, once it is merged into Axelrod. - ep = random.uniform(-1, 1) / 4 + def random_value(self): + return self._random.random() + + def mutate_value(self, value): + ep = self._random.uniform(-1, 1) / 4 value += ep if value < 0: value = 0 diff --git a/axelrod/strategies/hmm.py b/axelrod/strategies/hmm.py index 9a76e9360..fca48b69f 100644 --- a/axelrod/strategies/hmm.py +++ b/axelrod/strategies/hmm.py @@ -1,12 +1,6 @@ from axelrod.action import Action from axelrod.evolvable_player import EvolvablePlayer, InsufficientParametersError, copy_lists, crossover_lists from axelrod.player import Player -from axelrod.random_ import RandomGenerator - -# This instance is for the mutation methods. For reproducibility, -# random seed propagation should be refactored in the evolutionary -# algorithms. -random = RandomGenerator() C, D = Action.C, Action.D @@ -29,17 +23,17 @@ def normalize_vector(vec): return vec -def mutate_row(row, mutation_probability): +def mutate_row(row, mutation_probability, rng): """, crossover_lists_of_lists Given a row of probabilities, randomly change each entry with probability `mutation_probability` (a value between 0 and 1). If changing, then change by a value randomly (uniformly) chosen from [-0.25, 0.25] bounded by 0 and 100%. """ - randoms = random.random(len(row)) + randoms = rng.random(len(row)) for i in range(len(row)): if randoms[i] < mutation_probability: - ep = random.uniform(-1, 1) / 4 + ep = rng.uniform(-1, 1) / 4 row[i] += ep if row[i] < 0: row[i] = 0 @@ -190,7 +184,11 @@ def strategy(self, opponent: Player) -> Action: def set_seed(self, seed=None): super().set_seed(seed=seed) # Share RNG with HMM - self.hmm._random = self._random + # The evolvable version of the class needs to manually share the rng with the HMM. + try: + self.hmm._random = self._random + except AttributeError: + pass class EvolvableHMMPlayer(HMMPlayer, EvolvablePlayer): @@ -205,8 +203,10 @@ def __init__( initial_state=0, initial_action=C, num_states=None, - mutation_probability=None + mutation_probability=None, + seed: int = None ) -> None: + self.set_seed(seed=seed) transitions_C, transitions_D, emission_probabilities, initial_state, initial_action, num_states, mutation_probability = self._normalize_parameters( transitions_C, transitions_D, emission_probabilities, initial_state, initial_action, num_states, mutation_probability) self.mutation_probability = mutation_probability @@ -216,6 +216,7 @@ def __init__( emission_probabilities=emission_probabilities, initial_state=initial_state, initial_action=initial_action) + self.hmm._random = self._random EvolvablePlayer.__init__(self) self.overwrite_init_kwargs( transitions_C=transitions_C, @@ -227,13 +228,12 @@ def __init__( mutation_probability=mutation_probability ) - @classmethod - def _normalize_parameters(cls, transitions_C=None, transitions_D=None, emission_probabilities=None, + def _normalize_parameters(self, transitions_C=None, transitions_D=None, emission_probabilities=None, initial_state=None, initial_action=None, num_states=None, mutation_probability=None): - if not (transitions_C and transitions_D and emission_probabilities and (initial_state is not None) and (initial_action is not None)): + if not ((transitions_C and transitions_D and emission_probabilities) and (initial_state is not None) and (initial_action is not None)): if not num_states: raise InsufficientParametersError("Insufficient Parameters to instantiate EvolvableHMMPlayer") - transitions_C, transitions_D, emission_probabilities, initial_state, initial_action = cls.random_params( + transitions_C, transitions_D, emission_probabilities, initial_state, initial_action = self.random_params( num_states) # Normalize types of various matrices for m in [transitions_C, transitions_D]: @@ -247,16 +247,15 @@ def _normalize_parameters(cls, transitions_C=None, transitions_D=None, emission_ mutation_probability = mutation_probability return transitions_C, transitions_D, emission_probabilities, initial_state, initial_action, num_states, mutation_probability - @classmethod - def random_params(cls, num_states): + def random_params(self, num_states): transitions_C = [] transitions_D = [] emission_probabilities = [] for _ in range(num_states): - transitions_C.append(random.random_vector(num_states)) - transitions_D.append(random.random_vector(num_states)) - emission_probabilities.append(random.random()) - initial_state = random.randrange(num_states) + transitions_C.append(self._random.random_vector(num_states)) + transitions_D.append(self._random.random_vector(num_states)) + emission_probabilities.append(self._random.random()) + initial_state = self._random.randint(0, num_states) initial_action = C return transitions_C, transitions_D, emission_probabilities, initial_state, initial_action @@ -264,10 +263,9 @@ def random_params(cls, num_states): def num_states(self): return len(self.hmm.emission_probabilities) - @staticmethod - def mutate_rows(rows, mutation_probability): + def mutate_rows(self, rows, mutation_probability): for i, row in enumerate(rows): - row = mutate_row(row, mutation_probability) + row = mutate_row(row, mutation_probability, self._random) rows[i] = normalize_vector(row) return rows @@ -277,13 +275,13 @@ def mutate(self): transitions_D = self.mutate_rows( self.hmm.transitions_D, self.mutation_probability) emission_probabilities = mutate_row( - self.hmm.emission_probabilities, self.mutation_probability) + self.hmm.emission_probabilities, self.mutation_probability, self._random) initial_action = self.initial_action - if random.random() < self.mutation_probability / 10: + if self._random.random() < self.mutation_probability / 10: initial_action = self.initial_action.flip() initial_state = self.initial_state - if random.random() < self.mutation_probability / (10 * self.num_states): - initial_state = random.randrange(self.num_states) + if self._random.random() < self.mutation_probability / (10 * self.num_states): + initial_state = self._random.randint(0, self.num_states) return self.create_new( transitions_C=transitions_C, transitions_D=transitions_D, @@ -295,10 +293,10 @@ def mutate(self): def crossover(self, other): if other.__class__ != self.__class__: raise TypeError("Crossover must be between the same player classes.") - transitions_C = crossover_lists(self.hmm.transitions_C, other.hmm.transitions_C) - transitions_D = crossover_lists(self.hmm.transitions_D, other.hmm.transitions_D) + transitions_C = crossover_lists(self.hmm.transitions_C, other.hmm.transitions_C, self._random) + transitions_D = crossover_lists(self.hmm.transitions_D, other.hmm.transitions_D, self._random) emission_probabilities = crossover_lists( - self.hmm.emission_probabilities, other.hmm.emission_probabilities) + self.hmm.emission_probabilities, other.hmm.emission_probabilities, self._random) return self.create_new( transitions_C=transitions_C, transitions_D=transitions_D, diff --git a/axelrod/strategies/lookerup.py b/axelrod/strategies/lookerup.py index b66d06293..9249d3267 100644 --- a/axelrod/strategies/lookerup.py +++ b/axelrod/strategies/lookerup.py @@ -2,9 +2,6 @@ from itertools import product from typing import Any, TypeVar -import numpy.random as random -from numpy.random import choice - from axelrod.action import Action, actions_to_str, str_to_actions from axelrod.evolvable_player import EvolvablePlayer, InsufficientParametersError, crossover_dictionaries from axelrod.player import Player @@ -404,8 +401,10 @@ def __init__( initial_actions: tuple = None, pattern: Any = None, # pattern is str or tuple of Action's. parameters: Plays = None, - mutation_probability: float = None + mutation_probability: float = None, + seed: int = None ) -> None: + self.set_seed(seed=seed) lookup_dict, initial_actions, pattern, parameters, mutation_probability = self._normalize_parameters( lookup_dict, initial_actions, pattern, parameters, mutation_probability ) @@ -426,29 +425,28 @@ def __init__( mutation_probability=mutation_probability, ) - @classmethod - def _normalize_parameters(cls, lookup_dict=None, initial_actions=None, pattern=None, parameters=None, + def _normalize_parameters(self, lookup_dict=None, initial_actions=None, pattern=None, parameters=None, mutation_probability=None): if lookup_dict and initial_actions: # Compute the associated pattern and parameters # Map the table keys to namedTuple Plays - lookup_table = cls._get_lookup_table(lookup_dict, pattern, parameters) + lookup_table = self._get_lookup_table(lookup_dict, pattern, parameters) lookup_dict = lookup_table.dictionary parameters = (lookup_table.player_depth, lookup_table.op_depth, lookup_table.op_openings_depth) pattern = tuple(v for k, v in sorted(lookup_dict.items())) elif pattern and parameters and initial_actions: # Compute the associated lookup table plays, op_plays, op_start_plays = parameters - lookup_table = cls._get_lookup_table(lookup_dict, pattern, parameters) + lookup_table = self._get_lookup_table(lookup_dict, pattern, parameters) lookup_dict = lookup_table.dictionary elif parameters: # Generate a random pattern and (maybe) initial actions plays, op_plays, op_start_plays = parameters - pattern, lookup_table = cls.random_params(plays, op_plays, op_start_plays) + pattern, lookup_table = self.random_params(plays, op_plays, op_start_plays) lookup_dict = lookup_table.dictionary if not initial_actions: num_actions = max([plays, op_plays, op_start_plays]) - initial_actions = tuple([choice((C, D)) for _ in range(num_actions)]) + initial_actions = tuple([self._random.choice((C, D)) for _ in range(num_actions)]) else: raise InsufficientParametersError("Insufficient Parameters to instantiate EvolvableLookerUp") # Normalize pattern @@ -461,15 +459,13 @@ def _normalize_parameters(cls, lookup_dict=None, initial_actions=None, pattern=N mutation_probability = 2. / len(keys) return lookup_dict, initial_actions, pattern, parameters, mutation_probability - @classmethod - def random_value(cls): - return choice(actions) + def random_value(self): + return self._random.choice(actions) - @classmethod - def random_params(cls, plays, op_plays, op_start_plays): + def random_params(self, plays, op_plays, op_start_plays): keys = create_lookup_table_keys(plays, op_plays, op_start_plays) # To get a pattern, we just randomly pick between C and D for each key - pattern = [cls.random_value() for _ in keys] + pattern = [self.random_value() for _ in keys] table = dict(zip(keys, pattern)) return pattern, LookupTable(table) @@ -477,13 +473,12 @@ def random_params(cls, plays, op_plays, op_start_plays): def mutate_value(cls, value): return value.flip() - @classmethod - def mutate_table(cls, table, mutation_probability): - randoms = random.random(len(table.keys())) + def mutate_table(self, table, mutation_probability): + randoms = self._random.random(len(table.keys())) # Flip each value with a probability proportional to the mutation rate for i, (history, move) in enumerate(table.items()): if randoms[i] < mutation_probability: - table[history] = cls.mutate_value(move) + table[history] = self.mutate_value(move) return table def mutate(self): @@ -491,7 +486,7 @@ def mutate(self): # Add in starting moves initial_actions = list(self.initial_actions) for i in range(len(initial_actions)): - r = random.random() + r = self._random.random() if r < self.mutation_probability: initial_actions[i] = initial_actions[i].flip() return self.create_new( @@ -502,7 +497,7 @@ def mutate(self): def crossover(self, other): if other.__class__ != self.__class__: raise TypeError("Crossover must be between the same player classes.") - lookup_dict = crossover_dictionaries(self.lookup_dict, other.lookup_dict) + lookup_dict = crossover_dictionaries(self.lookup_dict, other.lookup_dict, self._random) return self.create_new(lookup_dict=lookup_dict) diff --git a/axelrod/tests/integration/test_filtering.py b/axelrod/tests/integration/test_filtering.py index bce495b76..57406484b 100644 --- a/axelrod/tests/integration/test_filtering.py +++ b/axelrod/tests/integration/test_filtering.py @@ -2,6 +2,7 @@ import warnings import axelrod as axl +from axelrod import filtered_strategies, short_run_time_strategies from axelrod.tests.property import strategy_lists from hypothesis import example, given, settings @@ -106,7 +107,7 @@ def test_makes_use_of_filtering(self, seed_, strategies): classifiers = [["game"], ["length"], ["game", "length"]] for classifier in classifiers: - axl.seed(seed_) + axelrod._module_random.seed(seed_) comprehension = set( [ s @@ -115,7 +116,7 @@ def test_makes_use_of_filtering(self, seed_, strategies): ] ) - axl.seed(seed_) + axelrod._module_random.seed(seed_) filterset = {"makes_use_of": classifier} filtered = set(axl.filtered_strategies(filterset, strategies=strategies)) diff --git a/axelrod/tests/integration/test_matches.py b/axelrod/tests/integration/test_matches.py index a627fe885..50f56dbf2 100644 --- a/axelrod/tests/integration/test_matches.py +++ b/axelrod/tests/integration/test_matches.py @@ -46,9 +46,8 @@ def test_outcome_repeats_stochastic(self, strategies, turns, seed): same result""" results = [] for _ in range(3): - axl.seed(seed) players = [s() for s in strategies] - results.append(axl.Match(players, turns).play()) + results.append(axelrod.Match(players, turns=turns, seed=seed).play()) self.assertEqual(results[0], results[1]) self.assertEqual(results[1], results[2]) diff --git a/axelrod/tests/integration/test_tournament.py b/axelrod/tests/integration/test_tournament.py index 2ab59c974..30886632a 100644 --- a/axelrod/tests/integration/test_tournament.py +++ b/axelrod/tests/integration/test_tournament.py @@ -107,7 +107,6 @@ def test_repeat_tournament_stochastic(self): """ files = [] for _ in range(2): - axl.seed(0) stochastic_players = [ s() for s in axl.short_run_time_strategies @@ -119,6 +118,7 @@ def test_repeat_tournament_stochastic(self): game=self.game, turns=2, repetitions=2, + seed=17 ) path = pathlib.Path("test_outputs/stochastic_tournament_{}.csv".format(_)) files.append(axl_filename(path)) @@ -164,8 +164,7 @@ def test_matches_have_different_length(self): p2 = axl.Cooperator() p3 = axl.Cooperator() players = [p1, p2, p3] - axl.seed(0) - tournament = axl.Tournament(players, prob_end=0.5, repetitions=2) + tournament = axelrod.Tournament(players, prob_end=0.5, repetitions=2, seed=3) results = tournament.play(progress_bar=False) # Check that match length are different across the repetitions self.assertNotEqual(results.match_lengths[0], results.match_lengths[1]) diff --git a/axelrod/tests/property.py b/axelrod/tests/property.py index 95d08e2b8..496af736f 100644 --- a/axelrod/tests/property.py +++ b/axelrod/tests/property.py @@ -124,6 +124,7 @@ def prob_end_tournaments( max_noise=1, min_repetitions=1, max_repetitions=20, + seed=None ): """ A hypothesis decorator to return a tournament, @@ -145,7 +146,9 @@ def prob_end_tournaments( min_repetitions : integer The minimum number of repetitions max_repetitions : integer - The maximum number of repetitions + The maximum number of + seed : integer + Random seed """ strategies = draw( strategy_lists(strategies=strategies, min_size=min_size, max_size=max_size) @@ -156,7 +159,7 @@ def prob_end_tournaments( noise = draw(floats(min_value=min_noise, max_value=max_noise)) tournament = axl.Tournament( - players, prob_end=prob_end, repetitions=repetitions, noise=noise + players, prob_end=prob_end, repetitions=repetitions, noise=noise, seed=seed ) return tournament @@ -208,7 +211,6 @@ def spatial_tournaments( lists( sampled_from(all_potential_edges), unique=True, - average_size=2 * len(players), ) ) @@ -276,7 +278,6 @@ def prob_end_spatial_tournaments( lists( sampled_from(all_potential_edges), unique=True, - average_size=2 * len(players), ) ) diff --git a/axelrod/tests/strategies/test_ann.py b/axelrod/tests/strategies/test_ann.py index 3a63d2131..251648173 100644 --- a/axelrod/tests/strategies/test_ann.py +++ b/axelrod/tests/strategies/test_ann.py @@ -98,14 +98,13 @@ class TestEvolvableANN(unittest.TestCase): player_class = axl.EvolvableANN def test_normalized_parameters(self): - # Must specify at least one of cycle or cycle_length self.assertRaises( InsufficientParametersError, - self.player_class._normalize_parameters + self.player_class(3, 3)._normalize_parameters, ) self.assertRaises( InsufficientParametersError, - self.player_class._normalize_parameters, + self.player_class(3, 3)._normalize_parameters, weights=nn_weights["Evolved ANN 5"][2] ) diff --git a/axelrod/tests/strategies/test_cycler.py b/axelrod/tests/strategies/test_cycler.py index 83de4f135..7753a9712 100644 --- a/axelrod/tests/strategies/test_cycler.py +++ b/axelrod/tests/strategies/test_cycler.py @@ -29,10 +29,11 @@ class TestAntiCycler(TestPlayer): } def test_has_no_cycles(self): - test_range = 100 + # test_range = 100 player = axl.AntiCycler() - for _ in range(test_range): - player.play(axl.Cooperator()) + opponent = axl.Cooperator() + match = axl.Match((player, opponent), turns=100) + match.play() contains_no_cycles = player.history for slice_at in range(1, len(contains_no_cycles) + 1): @@ -153,50 +154,46 @@ class TestEvolvableCycler(unittest.TestCase): def test_normalized_parameters(self): # Must specify at least one of cycle or cycle_length self.assertRaises( - InsufficientParametersError, self.player_class._normalize_parameters + InsufficientParametersError, + self.player_class ) self.assertRaises( InsufficientParametersError, - self.player_class._normalize_parameters, - cycle="", + self.player_class, + cycle="" ) self.assertRaises( InsufficientParametersError, - self.player_class._normalize_parameters, - cycle_length=0, + self.player_class, + cycle_length=0 ) cycle = "C" * random.randint(0, 20) + "D" * random.randint(0, 20) - self.assertEqual( - self.player_class._normalize_parameters(cycle=cycle), (cycle, len(cycle)) - ) + self.assertEqual(self.player_class(cycle=cycle)._normalize_parameters(cycle=cycle), + (cycle, len(cycle))) cycle_length = random.randint(1, 20) - random_cycle, cycle_length2 = self.player_class._normalize_parameters( - cycle_length=cycle_length - ) + random_cycle, cycle_length2 = self.player_class(cycle=cycle)._normalize_parameters(cycle_length=cycle_length) self.assertEqual(len(random_cycle), cycle_length) self.assertEqual(cycle_length, cycle_length2) def test_crossover_even_length(self): cycle1 = "C" * 6 cycle2 = "D" * 6 - cross_cycle = "CDDDDD" + cross_cycle = "CCCDDD" - player1 = self.player_class(cycle=cycle1) + player1 = self.player_class(cycle=cycle1, seed=3) player2 = self.player_class(cycle=cycle2) - axl.seed(3) crossed = player1.crossover(player2) self.assertEqual(cross_cycle, crossed.cycle) def test_crossover_odd_length(self): cycle1 = "C" * 7 cycle2 = "D" * 7 - cross_cycle = "CDDDDDD" + cross_cycle = "CCCDDDD" - player1 = self.player_class(cycle=cycle1) + player1 = self.player_class(cycle=cycle1, seed=5) player2 = self.player_class(cycle=cycle2) - axl.seed(3) crossed = player1.crossover(player2) self.assertEqual(cross_cycle, crossed.cycle) diff --git a/axelrod/tests/strategies/test_evolvable_player.py b/axelrod/tests/strategies/test_evolvable_player.py index 06ff85ea2..6370987cf 100644 --- a/axelrod/tests/strategies/test_evolvable_player.py +++ b/axelrod/tests/strategies/test_evolvable_player.py @@ -1,19 +1,15 @@ import unittest import functools -import random +import unittest import axelrod as axl from axelrod.action import Action -from axelrod.evolvable_player import copy_lists, crossover_lists, crossover_dictionaries +from axelrod.evolvable_player import copy_lists, crossover_dictionaries, crossover_lists from .test_player import TestPlayer C, D = Action.C, Action.D -def seed(x): - return - - def PartialClass(cls, **kwargs): class PartialedClass(cls): __init__ = functools.partialmethod( @@ -25,12 +21,12 @@ class PartialedClass(cls): class EvolvableTestOpponent(axl.EvolvablePlayer): name = "EvolvableTestOpponent" - def __init__(self, value=None): - super().__init__() + def __init__(self, value=None, seed=None): + super().__init__(seed=seed) if value: self.value = value else: - value = random.randint(2, 100) + value = self._random.randint(2, 100) self.value = value self.overwrite_init_kwargs(value=value) @@ -39,7 +35,7 @@ def strategy(opponent): return Action.C def mutate(self): - value = random.randint(2, 100) + value = self._random.randint(2, 100) return EvolvableTestOpponent(value) def crossover(self, other): @@ -55,8 +51,10 @@ class TestEvolvablePlayer(TestPlayer): parent_class = None init_parameters = dict() - def player(self): - return self.player_class(**self.init_parameters) + def player(self, seed=None): + params = self.init_parameters.copy() + params["seed"] = seed + return self.player_class(**params) def test_repr(self): """Test that the representation is correct.""" @@ -76,14 +74,12 @@ def test_randomization(self): """Test that randomization on initialization produces different strategies.""" if self.init_parameters: return - axl.seed(0) - player1 = self.player() - axl.seed(0) - player2 = self.player() + player1 = self.player(seed=0) + player2 = self.player(seed=0) self.assertEqual(player1, player2) + for seed_ in range(2, 20): - axl.seed(seed_) - player2 = self.player() + player2 = self.player(seed=seed_) if player1 != player2: return # Should never get here unless a change breaks the test, so don't include in coverage. @@ -93,30 +89,29 @@ def test_mutate_variations(self): """Generate many variations to test that mutate produces different strategies.""" if not self.init_parameters: return - axl.seed(100) variants_produced = False - for _ in range(2, 400): - player = self.player() + for seed in range(2, 400): + player = self.player(seed=seed) mutant = player.mutate() if player != mutant: variants_produced = True + break self.assertTrue(variants_produced) def test_mutate_and_clone(self): """Test that mutated players clone properly.""" - axl.seed(0) - player = self.player() + player = self.player(seed=0) mutant = player.clone().mutate() clone = mutant.clone() self.assertEqual(clone, mutant) def test_crossover(self): """Test that crossover produces different strategies.""" - for seed_ in range(20): - axl.seed(seed_) + rng = axl.RandomGenerator(seed=0) + for _ in range(20): players = [] for _ in range(2): - player = self.player() + player = self.player(seed=rng.random_seed_int()) # Mutate to randomize player = player.mutate() players.append(player) @@ -135,8 +130,7 @@ def test_crossover_mismatch(self): def test_serialization(self): """Serializing and deserializing should return the original player.""" - axl.seed(0) - player = self.player() + player = self.player(seed=0) serialized = player.serialize_parameters() deserialized_player = player.__class__.deserialize_parameters(serialized) self.assertEqual(player, deserialized_player) @@ -144,8 +138,7 @@ def test_serialization(self): def test_serialization_csv(self): """Serializing and deserializing should return the original player.""" - axl.seed(0) - player = self.player() + player = self.player(seed=0) serialized = player.serialize_parameters() s = "0, 1, {}, 3".format(serialized) s2 = s.split(',')[2] @@ -192,23 +185,23 @@ def test_crossover_lists(self): list1 = [[0, C, 1, D], [0, D, 0, D], [1, C, 1, C], [1, D, 1, D]] list2 = [[0, D, 1, C], [0, C, 0, C], [1, D, 1, D], [1, C, 1, C]] - axl.seed(0) - crossed = crossover_lists(list1, list2) + rng = axl.RandomGenerator(seed=100) + crossed = crossover_lists(list1, list2, rng) self.assertEqual(crossed, list1[:3] + list2[3:]) - axl.seed(1) - crossed = crossover_lists(list1, list2) + rng = axl.RandomGenerator(seed=101) + crossed = crossover_lists(list1, list2, rng) self.assertEqual(crossed, list1[:1] + list2[1:]) def test_crossover_dictionaries(self): dict1 = {'1': 1, '2': 2, '3': 3} dict2 = {'1': 'a', '2': 'b', '3': 'c'} - axl.seed(0) - crossed = crossover_dictionaries(dict1, dict2) + rng = axl.RandomGenerator(seed=100) + crossed = crossover_dictionaries(dict1, dict2, rng) self.assertEqual(crossed, {'1': 1, '2': 'b', '3': 'c'}) - axl.seed(1) - crossed = crossover_dictionaries(dict1, dict2) + rng = axl.RandomGenerator(seed=101) + crossed = crossover_dictionaries(dict1, dict2, rng) self.assertEqual(crossed, dict2) diff --git a/axelrod/tests/strategies/test_finite_state_machines.py b/axelrod/tests/strategies/test_finite_state_machines.py index 12fe52e5d..ccefa14f8 100644 --- a/axelrod/tests/strategies/test_finite_state_machines.py +++ b/axelrod/tests/strategies/test_finite_state_machines.py @@ -1050,11 +1050,11 @@ class TestEvolvableFSMPlayer(unittest.TestCase): def test_normalized_parameters(self): self.assertRaises( InsufficientParametersError, - self.player_class._normalize_parameters + self.player_class ) self.assertRaises( InsufficientParametersError, - self.player_class._normalize_parameters, + self.player_class, transitions=[[0, C, 1, D], [0, D, 0, D], [1, C, 1, C], [1, D, 1, D]] ) diff --git a/axelrod/tests/strategies/test_gambler.py b/axelrod/tests/strategies/test_gambler.py index 2749526a3..571189d38 100755 --- a/axelrod/tests/strategies/test_gambler.py +++ b/axelrod/tests/strategies/test_gambler.py @@ -531,8 +531,9 @@ def test_create_vector_bounds(self): self.assertEqual(len(ub), 8) def test_mutate_value_bounds(self): - self.assertEqual(axl.EvolvableGambler.mutate_value(2), 1) - self.assertEqual(axl.EvolvableGambler.mutate_value(-2), 0) + player = axl.EvolvableGambler(parameters=(1, 1, 1), seed=0) + self.assertEqual(player.mutate_value(2), 1) + self.assertEqual(player.mutate_value(-2), 0) class TestEvolvableGambler2(TestEvolvablePlayer): diff --git a/axelrod/tests/strategies/test_geller.py b/axelrod/tests/strategies/test_geller.py index e2b7bdf67..d9e5f9d2c 100644 --- a/axelrod/tests/strategies/test_geller.py +++ b/axelrod/tests/strategies/test_geller.py @@ -33,11 +33,11 @@ def setUp(self): super(TestGeller, self).setUp() def test_foil_strategy_inspection(self): - axl.seed(2) player = self.player() - self.assertEqual(player.foil_strategy_inspection(), D) - self.assertEqual(player.foil_strategy_inspection(), D) + player.set_seed(2) self.assertEqual(player.foil_strategy_inspection(), C) + self.assertEqual(player.foil_strategy_inspection(), C) + self.assertEqual(player.foil_strategy_inspection(), D) def test_strategy(self): """Should cooperate against cooperators and defect against defectors.""" @@ -60,15 +60,15 @@ def test_strategy_against_lookerup_players(self): def test_returns_foil_inspection_strategy_of_opponent(self): self.versus_test( axl.GellerDefector(), - expected_actions=[(D, D), (D, D), (D, C), (D, C)], + expected_actions=[(D, D), (D, C), (D, D), (D, C)], seed=2, ) - self.versus_test(axl.Darwin(), expected_actions=[(C, C), (C, C), (C, C)]) + self.versus_test(axl.Darwin(), expected_actions=[(C, C), (C, C), (C, C)], seed=3) - self.versus_test( - axl.MindReader(), expected_actions=[(D, D), (D, D), (D, D)], seed=1 - ) + # self.versus_test( + # axelrod.MindReader(), expected_actions=[(D, D), (D, D), (D, D)], seed=1 + # ) class TestGellerCooperator(TestGeller): diff --git a/axelrod/tests/strategies/test_hmm.py b/axelrod/tests/strategies/test_hmm.py index 558da9bf4..a5adea8e9 100644 --- a/axelrod/tests/strategies/test_hmm.py +++ b/axelrod/tests/strategies/test_hmm.py @@ -1,17 +1,9 @@ """Tests for Hidden Markov Model Strategies.""" - import unittest -import random import axelrod as axl from axelrod.evolvable_player import InsufficientParametersError -from axelrod.strategies.hmm import ( - EvolvableHMMPlayer, - HMMPlayer, - SimpleHMM, - is_stochastic_matrix, - random_vector, -) +from axelrod.strategies.hmm import EvolvableHMMPlayer, HMMPlayer, SimpleHMM, is_stochastic_matrix from .test_player import TestMatch, TestPlayer from .test_evolvable_player import PartialClass, TestEvolvablePlayer @@ -50,8 +42,8 @@ def test_cooperator(self): self.assertFalse(player.is_stochastic()) self.assertFalse(axl.Classifiers["stochastic"](player)) opponent = axl.Alternator() - for i in range(6): - player.play(opponent) + match = axl.Match((player, opponent), turns=6) + match.play() self.assertEqual(opponent.history, [C, D] * 3) self.assertEqual(player.history, [C] * 6) @@ -71,8 +63,8 @@ def test_defector(self): self.assertFalse(player.is_stochastic()) self.assertFalse(axl.Classifiers["stochastic"](player)) opponent = axl.Alternator() - for i in range(6): - player.play(opponent) + match = axl.Match((player, opponent), turns=6) + match.play() self.assertEqual(opponent.history, [C, D] * 3) self.assertEqual(player.history, [D] * 6) @@ -92,14 +84,14 @@ def test_tft(self): self.assertFalse(player.is_stochastic()) self.assertFalse(axl.Classifiers["stochastic"](player)) opponent = axl.Alternator() - for i in range(6): - player.play(opponent) + match = axl.Match((player, opponent), turns=6) + match.play() self.assertEqual(opponent.history, [C, D] * 3) self.assertEqual(player.history, [C, C, D, C, D, C]) def test_wsls(self): - """Tests that the player defined by the table for TFT is in fact - WSLS (also known as Pavlov.""" + """Tests that the player defined by the table for WSLS is in fact + WSLS (also known as Pavlov).""" t_C = [[1, 0], [0, 1]] t_D = [[0, 1], [1, 0]] p = [1, 0] @@ -113,8 +105,8 @@ def test_wsls(self): self.assertFalse(player.is_stochastic()) self.assertFalse(axl.Classifiers["stochastic"](player)) opponent = axl.Alternator() - for i in range(6): - player.play(opponent) + match = axl.Match((player, opponent), turns=6) + match.play() self.assertEqual(opponent.history, [C, D] * 3) self.assertEqual(player.history, [C, C, D, D, C, C]) @@ -128,18 +120,22 @@ def test_malformed_params(self): p = [1, 0] hmm = SimpleHMM(t_C, t_C, p, 0) self.assertTrue(hmm.is_well_formed()) + hmm = SimpleHMM(t_C, t_D, p, -1) self.assertFalse(hmm.is_well_formed()) + t_C = [[1, -1], [0, 1]] t_D = [[0, 1], [1, 0]] p = [1, 0] hmm = SimpleHMM(t_C, t_D, p, 0) self.assertFalse(hmm.is_well_formed()) + t_C = [[1, 0], [0, 1]] t_D = [[0, 2], [1, 0]] p = [1, 0] hmm = SimpleHMM(t_C, t_D, p, 0) self.assertFalse(hmm.is_well_formed()) + t_C = [[1, 0], [0, 1]] t_D = [[0, 1], [1, 0]] p = [-1, 2] @@ -216,18 +212,28 @@ def test_normalized_parameters(self): initial_action = C self.assertRaises( - InsufficientParametersError, self.player_class._normalize_parameters + InsufficientParametersError, + self.player_class + ) + self.assertRaises( + InsufficientParametersError, + self.player_class, + transitions_C=transitions_C, + transitions_D=transitions_D, + emission_probabilities=emission_probabilities, + initial_state=None ) self.assertRaises( InsufficientParametersError, - self.player_class._normalize_parameters, + self.player_class, transitions_C=transitions_C, transitions_D=transitions_D, emission_probabilities=emission_probabilities, + initial_action=None ) self.assertRaises( InsufficientParametersError, - self.player_class._normalize_parameters, + self.player_class, initial_state=initial_state, initial_action=initial_action, ) @@ -235,10 +241,11 @@ def test_normalized_parameters(self): def test_vector_to_instance(self): num_states = 4 vector = [] + rng = axl.RandomGenerator(seed=1111) for _ in range(2 * num_states): - vector.extend(list(random_vector(num_states))) + vector.extend(list(rng.random_vector(num_states))) for _ in range(num_states + 1): - vector.append(random.random()) + vector.append(rng.random()) player = self.player_class(num_states=num_states) player.receive_vector(vector=vector) self.assertIsInstance(player, self.player_class) diff --git a/axelrod/tests/strategies/test_human.py b/axelrod/tests/strategies/test_human.py index 46c1f3f8a..1d9112cd5 100644 --- a/axelrod/tests/strategies/test_human.py +++ b/axelrod/tests/strategies/test_human.py @@ -131,3 +131,6 @@ def test_repr(self): def equality_of_players_test(self, p1, p2, seed, opponent): return True + + def test_reproducibility_of_play(self): + return True diff --git a/axelrod/tests/strategies/test_hunter.py b/axelrod/tests/strategies/test_hunter.py index 7c2912494..08745f270 100644 --- a/axelrod/tests/strategies/test_hunter.py +++ b/axelrod/tests/strategies/test_hunter.py @@ -2,9 +2,8 @@ import unittest -import random - import axelrod as axl +from axelrod import Match from axelrod.strategies.hunter import detect_cycle from .test_player import TestPlayer @@ -136,11 +135,10 @@ def test_strategy(self): axl.Alternator(), ]: player.reset() - for i in range(30): - player.play(opponent) + match = Match((player, opponent), turns=30) + match.play() self.assertEqual(player.history[-1], D) # Test against non-cyclers - axl.seed(40) for opponent in [ axl.Random(), axl.AntiCycler(), @@ -148,8 +146,8 @@ def test_strategy(self): axl.Defector(), ]: player.reset() - for i in range(30): - player.play(opponent) + match = Match((player, opponent), turns=30, seed=40) + match.play() self.assertEqual(player.history[-1], C) def test_reset_attr(self): @@ -183,11 +181,10 @@ def test_strategy(self): axl.Alternator(), ]: player.reset() - for i in range(50): - player.play(opponent) + match = Match((player, opponent), turns=50) + match.play() self.assertEqual(player.history[-1], D) # Test against non-cyclers and cooperators - axl.seed(43) for opponent in [ axl.Random(), axl.AntiCycler(), @@ -195,8 +192,8 @@ def test_strategy(self): axl.Cooperator(), ]: player.reset() - for i in range(50): - player.play(opponent) + match = Match((player, opponent), turns=50, seed=43) + match.play() self.assertEqual(player.history[-1], C) def test_reset_attr(self): @@ -260,8 +257,8 @@ def test_strategy(self): def test_reset(self): player = self.player() opponent = axl.Cooperator() - for _ in range(100): - player.play(opponent) + match = Match((player, opponent), turns=100) + match.play() self.assertFalse(player.countCC == 0) player.reset() self.assertTrue(player.countCC == 0) diff --git a/axelrod/tests/strategies/test_lookerup.py b/axelrod/tests/strategies/test_lookerup.py index 024328c93..c071edf3a 100755 --- a/axelrod/tests/strategies/test_lookerup.py +++ b/axelrod/tests/strategies/test_lookerup.py @@ -667,17 +667,18 @@ def test_normalized_parameters(self): pattern = ("".join([random.choice(("C", "D")) for _ in range(8)]),) self.assertRaises( - InsufficientParametersError, self.player_class._normalize_parameters + InsufficientParametersError, + self.player_class ) self.assertRaises( InsufficientParametersError, - self.player_class._normalize_parameters, + self.player_class, pattern=pattern, initial_actions=initial_actions, ) self.assertRaises( InsufficientParametersError, - self.player_class._normalize_parameters, + self.player_class, lookup_dict=lookup_dict, ) diff --git a/axelrod/tests/strategies/test_memorytwo.py b/axelrod/tests/strategies/test_memorytwo.py index 3318c6983..32bc6b9d0 100644 --- a/axelrod/tests/strategies/test_memorytwo.py +++ b/axelrod/tests/strategies/test_memorytwo.py @@ -1,9 +1,6 @@ """Tests for the Memorytwo strategies.""" import unittest - -import random - import warnings import axelrod as axl @@ -156,10 +153,10 @@ class TestMemoryStochastic(TestPlayer): } def test_strategy(self): - axl.seed(0) - vector = [random.random() for _ in range(16)] + rng = axelrod.RandomGenerator(seed=7888) + vector = [rng.random() for _ in range(16)] - actions = [(C, C), (C, C), (D, D), (D, C), (C, C), (C, D), (C, C)] + actions = [(C, C), (C, C), (D, D), (C, C), (D, C), (D, D), (D, C)] self.versus_test( opponent=axl.CyclerCCD(), expected_actions=actions, @@ -167,7 +164,7 @@ def test_strategy(self): init_kwargs={"sixteen_vector": vector}, ) - actions = [(C, C), (C, C), (C, D), (D, C), (C, C), (C, D), (C, C)] + actions = [(C, C), (C, C), (C, D), (C, C), (D, C), (D, D), (D, C)] self.versus_test( opponent=axl.CyclerCCD(), expected_actions=actions, @@ -175,7 +172,7 @@ def test_strategy(self): init_kwargs={"sixteen_vector": vector}, ) - actions = [(C, C), (C, C), (D, C), (D, D), (C, D), (C, C), (D, C)] + actions = [(C, C), (C, C), (D, C), (D, D), (D, D), (D, D), (D, D)] self.versus_test( opponent=axl.TitForTat(), expected_actions=actions, @@ -183,7 +180,7 @@ def test_strategy(self): init_kwargs={"sixteen_vector": vector}, ) - actions = [(C, C), (C, C), (C, C), (D, C), (D, D), (C, D), (C, C)] + actions = [(C, C), (C, C), (C, C), (D, C), (D, D), (D, D), (D, D)] self.versus_test( opponent=axl.TitForTat(), expected_actions=actions, diff --git a/axelrod/tests/strategies/test_meta.py b/axelrod/tests/strategies/test_meta.py index b9dbe9fef..e1e49dc18 100644 --- a/axelrod/tests/strategies/test_meta.py +++ b/axelrod/tests/strategies/test_meta.py @@ -64,7 +64,7 @@ def test_repr(self): ) @given(seed=integers(min_value=1, max_value=20000000)) - @settings(max_examples=1) + @settings(max_examples=1, deadline=None) def test_clone(self, seed): # Test that the cloned player produces identical play player1 = self.player() @@ -179,20 +179,20 @@ def test_strategy(self): opponent = axl.Cooperator() player = axl.NiceMetaWinner(team=[axl.Cooperator, axl.Defector]) - for _ in range(5): - player.play(opponent) + match = axl.Match((player, opponent), turns=5) + match.play() self.assertEqual(player.history[-1], C) opponent = axl.Defector() player = axl.NiceMetaWinner(team=[axl.Defector]) - for _ in range(20): - player.play(opponent) + match = axl.Match((player, opponent), turns=20) + match.play() self.assertEqual(player.history[-1], D) opponent = axl.Defector() player = axl.MetaWinner(team=[axl.Cooperator, axl.Defector]) - for _ in range(20): - player.play(opponent) + match = axl.Match((player, opponent), turns=20) + match.play() self.assertEqual(player.history[-1], D) @@ -494,8 +494,6 @@ def test_strategy(self): team = [axl.TitForTat, axl.Cooperator, axl.Grudger] distribution = [0.2, 0.5, 0.3] - P1 = axl.MetaMixer(team=team, distribution=distribution) - P2 = axl.Cooperator() actions = [(C, C)] * 20 self.versus_test( opponent=axl.Cooperator(), @@ -524,8 +522,8 @@ def test_raise_error_in_distribution(self): distribution = [0.2, 0.5, 0.5] # Not a valid probability distribution player = axl.MetaMixer(team=team, distribution=distribution) + player.set_seed(100) opponent = axl.Cooperator() - self.assertRaises(ValueError, player.strategy, opponent) diff --git a/axelrod/tests/strategies/test_mindreader.py b/axelrod/tests/strategies/test_mindreader.py index 21a78ec6a..c582ce538 100644 --- a/axelrod/tests/strategies/test_mindreader.py +++ b/axelrod/tests/strategies/test_mindreader.py @@ -1,6 +1,6 @@ """Tests for the Mindreader strategy.""" -import axelrod as axl +import axl as axl from axelrod._strategy_utils import simulate_match from .test_player import TestPlayer @@ -85,8 +85,8 @@ def test_vs_geller(self): """Ensures that a recursion error does not occur """ p1 = axl.MindReader() p2 = axl.Geller() - p1.strategy(p2) - p2.strategy(p1) + match = axl.Match((p1, p2), turns=5, seed=4) + match.play() def test_init(self): """Tests for init method """ diff --git a/axelrod/tests/strategies/test_oncebitten.py b/axelrod/tests/strategies/test_oncebitten.py index c152bce10..1d5d7e301 100644 --- a/axelrod/tests/strategies/test_oncebitten.py +++ b/axelrod/tests/strategies/test_oncebitten.py @@ -1,7 +1,5 @@ """Tests for the once bitten strategy.""" -import random - import axelrod as axl from .test_player import TestPlayer @@ -68,9 +66,8 @@ def test_reset(self): """Check that grudged gets reset properly""" p1 = self.player() p2 = axl.Defector() - p1.play(p2) - p1.play(p2) - p1.play(p2) + match = axl.Match((p1, p2), turns=3, seed=0) + match.play() self.assertTrue(p1.grudged) p1.reset() self.assertFalse(p1.grudged) @@ -133,10 +130,10 @@ def test_strategy(self): ) # Sometime eventually forget count: - actions = [(C, D), (C, D)] + [(D, D)] * 18 + [(C, D)] + actions = [(C, D), (C, D)] + [(D, D)] * 3 + [(C, D)] self.versus_test( opponent=axl.Defector(), expected_actions=actions, - seed=2, + seed=4, attrs={"D_count": 0}, ) diff --git a/axelrod/tests/strategies/test_player.py b/axelrod/tests/strategies/test_player.py index 9e690d3d6..0bbbb75ee 100644 --- a/axelrod/tests/strategies/test_player.py +++ b/axelrod/tests/strategies/test_player.py @@ -63,10 +63,12 @@ def test_play(self): player1, player2 = self.player(), self.player() player1.strategy = cooperate player2.strategy = defect - player1.play(player2) + + match = axl.Match((player1, player2), turns=1) + match.play() + self.assertEqual(player1.history[0], C) self.assertEqual(player2.history[0], D) - # Test cooperation / defection counts self.assertEqual(player1.cooperations, 1) self.assertEqual(player1.defections, 0) @@ -76,7 +78,8 @@ def test_play(self): self.assertEqual(player1.state_distribution, {(C, D): 1}) self.assertEqual(player2.state_distribution, {(D, C): 1}) - player1.play(player2) + match = axl.Match((player1, player2), turns=2) + match.play() self.assertEqual(player1.history[-1], C) self.assertEqual(player2.history[-1], D) # Test cooperation / defection counts @@ -102,16 +105,6 @@ def test_state_distribution(self): {(C, C): 1, (C, D): 1, (D, C): 2, (D, D): 1}, ) - def test_noisy_play(self): - noise = 0.2 - player1, player2 = self.player(), self.player() - player1.strategy = cooperate - player2.strategy = defect - match = axl.Match((player1, player2), turns=1, seed=1) - match.play() - self.assertEqual(player1.history[0], D) - self.assertEqual(player2.history[0], D) - def test_update_history(self): player = axl.Player() self.assertEqual(player.history, []) @@ -413,7 +406,7 @@ def equality_of_players_test(self, p1, p2, seed, opponent): a2 = opponent() self.assertEqual(p1, p2) for player, op in [(p1, a1), (p2, a2)]: - m = axelrod.Match(players=(player, op), turns=10, seed=seed) + m = axl.Match(players=(player, op), turns=10, seed=seed) m.play() self.assertEqual(p1, p2) p1 = pickle.loads(pickle.dumps(p1)) @@ -424,7 +417,7 @@ def equality_of_players_test(self, p1, p2, seed, opponent): opponent=sampled_from(short_run_time_short_mem), seed=integers(min_value=1, max_value=200), ) - @settings(max_examples=1) + @settings(max_examples=1, deadline=None) def test_equality_of_clone(self, seed, opponent): p1 = self.player() p2 = p1.clone() @@ -434,7 +427,7 @@ def test_equality_of_clone(self, seed, opponent): opponent=sampled_from(axl.short_run_time_strategies), seed=integers(min_value=1, max_value=200), ) - @settings(max_examples=1) + @settings(max_examples=1, deadline=None) def test_equality_of_pickle_clone(self, seed, opponent): p1 = self.player() p2 = pickle.loads(pickle.dumps(p1)) @@ -465,10 +458,10 @@ def test_reset_clone(self): def test_reproducibility_of_play(self): player = self.player() player_clone = player.clone() - coplayer = axelrod.Random(0.5) + coplayer = axl.Random(0.5) coplayer_clone = coplayer.clone() - m1 = axelrod.Match((player, coplayer), turns=10, seed=10) - m2 = axelrod.Match((player_clone, coplayer_clone), turns=10, seed=10) + m1 = axl.Match((player, coplayer), turns=10, seed=10) + m2 = axl.Match((player_clone, coplayer_clone), turns=10, seed=10) m1.play() m2.play() self.assertEqual(m1.result, m2.result) @@ -658,18 +651,6 @@ def versus_test( match.play() self.assertEqual(match.result, list(zip(expected_actions1, expected_actions2))) - # # Test expected sequence of play. - # for expected_result, result in zip( - # zip(expected_actions1, expected_actions2), - # match.result): - # self.assertEqual() - # # player1.play(player2) - # # self.assertEqual(player1.history[i], outcome1) - # # self.assertEqual(player2.history[i], outcome2) - # self.assertEqual(match.result[i], outcome1) - # self.assertEqual(player2.history[i], outcome2) - - def test_versus_with_incorrect_history_lengths(self): """Test the error raised by versus_test if expected actions do not match up""" diff --git a/axelrod/tests/strategies/test_stalker.py b/axelrod/tests/strategies/test_stalker.py index cc013543c..37ab36a7f 100644 --- a/axelrod/tests/strategies/test_stalker.py +++ b/axelrod/tests/strategies/test_stalker.py @@ -85,9 +85,8 @@ def test_strategy(self): ) def test_reset(self): - axl.seed(0) player = axl.Stalker() - m = axl.Match((player, axl.Alternator())) + m = axl.Match((player, axl.Alternator()), seed=0) m.play() self.assertNotEqual(player.current_score, 0) player.reset() diff --git a/axelrod/tests/strategies/test_titfortat.py b/axelrod/tests/strategies/test_titfortat.py index b99522d5f..a0a795837 100644 --- a/axelrod/tests/strategies/test_titfortat.py +++ b/axelrod/tests/strategies/test_titfortat.py @@ -1,13 +1,10 @@ """Tests for the tit for tat strategies.""" import copy - -import random - import axelrod as axl from axelrod.tests.property import strategy_lists -from hypothesis import given +from hypothesis import given, settings from hypothesis.strategies import integers from .test_player import TestPlayer @@ -53,10 +50,10 @@ def test_strategy(self): ) # We can also test against random strategies - actions = [(C, D), (D, D), (D, C), (C, C), (C, D)] + actions = [(C, D), (D, C), (C, C), (C, D), (D, D)] self.versus_test(axl.Random(), expected_actions=actions, seed=0) - actions = [(C, C), (C, D), (D, D), (D, C)] + actions = [(C, C), (C, C), (C, C), (C, C)] self.versus_test(axl.Random(), expected_actions=actions, seed=1) # If you would like to test against a sequence of moves you should use @@ -147,7 +144,7 @@ def test_strategy(self): actions = [(C, D), (D, C), (C, D), (D, D), (D, C)] self.versus_test(opponent, expected_actions=actions, seed=1) # Should respond differently with a different seed - actions = [(C, D), (D, C), (D, D), (D, D), (C, C)] + actions = [(C, D), (D, C), (D, D), (D, D), (D, C)] self.versus_test(opponent, expected_actions=actions, seed=2) # Will cooperate if opponent cooperates. @@ -717,8 +714,9 @@ def test_init(self): @given( strategies=strategy_lists(strategies=deterministic_strategies, max_size=1), - turns=integers(min_value=1, max_value=20), + turns=integers(min_value=1, max_value=20) ) + @settings(deadline=None) def test_is_tit_for_tat_with_no_noise(self, strategies, turns): tft = axl.TitForTat() ctft = self.player() @@ -728,39 +726,39 @@ def test_is_tit_for_tat_with_no_noise(self, strategies, turns): self.assertEqual(m1.play(), m2.play()) def test_strategy_with_noise(self): - ctft = self.player() + player = self.player() opponent = axl.Defector() - self.assertEqual(ctft.strategy(opponent), C) - self.assertEqual(ctft._recorded_history, [C]) - ctft.reset() # Clear the recorded history - self.assertEqual(ctft._recorded_history, []) - - random.seed(0) - ctft.play(opponent, noise=0.9) - self.assertEqual(ctft.history, [D]) - self.assertEqual(ctft._recorded_history, [C]) + match = axl.Match((player, opponent), turns=1, seed=9) + match.play() + self.assertEqual(player.history[-1], C) + self.assertEqual(player._recorded_history, [C]) + + match = axl.Match((player, opponent), turns=1, noise=0.9, seed=9) + match.play() + self.assertEqual(player.history, [D]) + self.assertEqual(player._recorded_history, [C]) self.assertEqual(opponent.history, [C]) - # After noise: is contrite - ctft.play(opponent) - self.assertEqual(ctft.history, [D, C]) - self.assertEqual(ctft._recorded_history, [C, C]) - self.assertEqual(opponent.history, [C, D]) - self.assertTrue(ctft.contrite) - - # Cooperates and no longer contrite - ctft.play(opponent) - self.assertEqual(ctft.history, [D, C, C]) - self.assertEqual(ctft._recorded_history, [C, C, C]) - self.assertEqual(opponent.history, [C, D, D]) - self.assertFalse(ctft.contrite) - - # Goes back to playing tft - ctft.play(opponent) - self.assertEqual(ctft.history, [D, C, C, D]) - self.assertEqual(ctft._recorded_history, [C, C, C, D]) - self.assertEqual(opponent.history, [C, D, D, D]) - self.assertFalse(ctft.contrite) + # # After noise: is contrite + # ctft.play(opponent) + # self.assertEqual(ctft.history, [D, C]) + # self.assertEqual(ctft._recorded_history, [C, C]) + # self.assertEqual(opponent.history, [C, D]) + # self.assertTrue(ctft.contrite) + # + # # Cooperates and no longer contrite + # ctft.play(opponent) + # self.assertEqual(ctft.history, [D, C, C]) + # self.assertEqual(ctft._recorded_history, [C, C, C]) + # self.assertEqual(opponent.history, [C, D, D]) + # self.assertFalse(ctft.contrite) + # + # # Goes back to playing tft + # ctft.play(opponent) + # self.assertEqual(ctft.history, [D, C, C, D]) + # self.assertEqual(ctft._recorded_history, [C, C, C, D]) + # self.assertEqual(opponent.history, [C, D, D, D]) + # self.assertFalse(ctft.contrite) class TestAdaptiveTitForTat(TestPlayer): @@ -979,13 +977,13 @@ def test_strategy(self): match_attributes={"length": float("inf")}, init_kwargs=init_kwargs, ) - actions = [(C, D), (D, D), (D, C), (C, C), (C, D)] + actions = [(C, D), (D, C), (C, C), (C, D), (D, D)] self.versus_test( axl.Random(), expected_actions=actions, seed=0, init_kwargs=init_kwargs ) - actions = [(C, C), (C, D), (D, D), (D, C)] + actions = [(C, D), (D, C), (C, D), (D, D)] self.versus_test( - axl.Random(), expected_actions=actions, seed=1, init_kwargs=init_kwargs + axl.Random(), expected_actions=actions, seed=2, init_kwargs=init_kwargs ) opponent = axl.MockPlayer(actions=[C, D]) actions = [(C, C), (C, D), (D, C), (C, D)] @@ -1173,7 +1171,7 @@ def test_strategy(self): actions = [(C, D), (D, D), (C, D), (D, D)] self.versus_test(axl.Defector(), expected_actions=actions, init_kwargs={"p": 1}) - actions = [(C, C), (C, C), (D, C), (C, C), (D, C), (C, C)] + actions = [(C, C), (C, C), (D, C), (C, C), (C, C), (C, C)] self.versus_test(axl.Cooperator(), expected_actions=actions, seed=2) actions = [(C, D), (D, D), (C, D), (D, D), (D, D), (D, D)] diff --git a/axelrod/tests/unit/test_fingerprint.py b/axelrod/tests/unit/test_fingerprint.py index f60a93e7a..0cd437cac 100644 --- a/axelrod/tests/unit/test_fingerprint.py +++ b/axelrod/tests/unit/test_fingerprint.py @@ -10,7 +10,6 @@ import axelrod as axl from axelrod.fingerprint import AshlockFingerprint, Point, TransitiveFingerprint from axelrod.load_data_ import axl_filename -from axelrod.strategy_transformers import DualTransformer, JossAnnTransformer from axelrod.tests.property import strategy_lists from hypothesis import given, settings @@ -247,9 +246,8 @@ def test_parallel_fingerprint(self): self.assertEqual(coord_keys, self.points_when_using_half_step) def test_plot_data(self): - axl.seed(0) # Fingerprinting is a random process. af = AshlockFingerprint(axl.Cooperator()) - af.fingerprint(turns=5, repetitions=3, step=0.5, progress_bar=False) + af.fingerprint(turns=5, repetitions=3, step=0.5, progress_bar=False, seed=0) reshaped_data = np.array([[0.0, 0.0, 0.0], [2.0, 1.0, 2.0], [3.0, 3.0, 3.0]]) plotted_data = af.plot().gca().images[0].get_array() @@ -272,7 +270,6 @@ def test_plot_figure(self): self.assertIsInstance(v, matplotlib.pyplot.Figure) def test_wsls_fingerprint(self): - axl.seed(0) # Fingerprinting is a random process. test_data = { Point(x=0.0, y=0.0): 3.000, Point(x=0.0, y=0.25): 1.710, @@ -301,13 +298,13 @@ def test_wsls_fingerprint(self): Point(x=1.0, y=1.0): 1.300, } af = axl.AshlockFingerprint(axl.WinStayLoseShift(), axl.TitForTat) - data = af.fingerprint(turns=50, repetitions=2, step=0.25, progress_bar=False) + data = af.fingerprint(turns=50, repetitions=2, step=0.25, progress_bar=False, + seed=0) for key, value in data.items(): self.assertAlmostEqual(value, test_data[key], places=2) def test_tft_fingerprint(self): - axl.seed(0) # Fingerprinting is a random process. test_data = { Point(x=0.0, y=0.0): 3.000, Point(x=0.0, y=0.25): 1.820, @@ -337,13 +334,13 @@ def test_tft_fingerprint(self): } af = axl.AshlockFingerprint(axl.TitForTat(), axl.TitForTat) - data = af.fingerprint(turns=50, repetitions=2, step=0.25, progress_bar=False) + data = af.fingerprint(turns=50, repetitions=2, step=0.25, progress_bar=False, + seed=0) for key, value in data.items(): self.assertAlmostEqual(value, test_data[key], places=2) def test_majority_fingerprint(self): - axl.seed(0) # Fingerprinting is a random process. test_data = { Point(x=0.0, y=0.0): 3.000, Point(x=0.0, y=0.25): 1.940, @@ -373,13 +370,14 @@ def test_majority_fingerprint(self): } af = axl.AshlockFingerprint(axl.GoByMajority, axl.TitForTat) - data = af.fingerprint(turns=50, repetitions=2, step=0.25, progress_bar=False) + data = af.fingerprint(turns=50, repetitions=2, step=0.25, progress_bar=False, + seed=0) for key, value in data.items(): self.assertAlmostEqual(value, test_data[key], places=2) @given(strategy_pair=strategy_lists(min_size=2, max_size=2)) - @settings(max_examples=5) + @settings(max_examples=5, deadline=None) def test_pair_fingerprints(self, strategy_pair): """ A test to check that we can fingerprint diff --git a/axelrod/tests/unit/test_history.py b/axelrod/tests/unit/test_history.py index 7c517958b..29b088097 100644 --- a/axelrod/tests/unit/test_history.py +++ b/axelrod/tests/unit/test_history.py @@ -68,8 +68,8 @@ def test_counts(self): def test_flip_plays(self): player = axl.Alternator() opponent = axl.Cooperator() - for _ in range(5): - player.play(opponent) + match = axl.Match((player, opponent), turns=5) + match.play() self.assertEqual(player.history, [C, D, C, D, C]) self.assertEqual(player.cooperations, 3) diff --git a/axelrod/tests/unit/test_match.py b/axelrod/tests/unit/test_match.py index 812fd79f3..82df07bd4 100644 --- a/axelrod/tests/unit/test_match.py +++ b/axelrod/tests/unit/test_match.py @@ -3,12 +3,14 @@ from collections import Counter import axelrod as axl +from hypothesis import example, given +from hypothesis.strategies import floats, integers + +from axelrod import Action from axelrod.deterministic_cache import DeterministicCache from axelrod.random_ import RandomGenerator from axelrod.tests.property import games -from hypothesis import example, given -from hypothesis.strategies import assume, floats, integers C, D = axl.Action.C, axl.Action.D @@ -81,18 +83,10 @@ def test_example_prob_end(self): Test that matches have diff length and also that cache has recorded the outcomes """ -<<<<<<< HEAD p1, p2 = axl.Cooperator(), axl.Cooperator() - match = axl.Match((p1, p2), prob_end=0.5) - expected_lengths = [3, 1, 5] - for seed, expected_length in zip(range(3), expected_lengths): - axl.seed(seed) -======= - p1, p2 = axelrod.Cooperator(), axelrod.Cooperator() expected_lengths = [2, 1, 1] for seed, expected_length in zip(range(3), expected_lengths): - match = axelrod.Match((p1, p2), prob_end=0.5, seed=seed) ->>>>>>> First pass on reproducible matches and parallel tournaments with random seeding + match = axl.Match((p1, p2), prob_end=0.5, seed=seed) self.assertEqual(match.players[0].match_attributes["length"], float("inf")) self.assertEqual(len(match.play()), expected_length) self.assertEqual(match.noise, 0) @@ -127,11 +121,8 @@ def test_len_error(self): with self.assertRaises(TypeError): len(match) - @given(p=floats(min_value=0, max_value=1)) + @given(p=floats(min_value=1e-10, max_value=1-1e-10)) def test_stochastic(self, p): - - assume(0 < p < 1) - p1, p2 = axl.Cooperator(), axl.Cooperator() match = axl.Match((p1, p2), 5) self.assertFalse(match._stochastic) @@ -143,11 +134,8 @@ def test_stochastic(self, p): match = axl.Match((p1, p2), 5) self.assertTrue(match._stochastic) - @given(p=floats(min_value=0, max_value=1)) + @given(p=floats(min_value=1e-10, max_value=1-1e-10)) def test_cache_update_required(self, p): - - assume(0 < p < 1) - p1, p2 = axl.Cooperator(), axl.Cooperator() match = axl.Match((p1, p2), 5, noise=p) self.assertFalse(match._cache_update_required) @@ -375,23 +363,12 @@ def test_sample_length(self): (2, 0.6, 1), (3, 0.4, 2), ]: -<<<<<<< HEAD - axl.seed(seed) - self.assertEqual(axl.match.sample_length(prob_end), expected_length) - - def test_sample_with_0_prob(self): - self.assertEqual(axl.match.sample_length(0), float("inf")) - - def test_sample_with_1_prob(self): - self.assertEqual(axl.match.sample_length(1), 1) -======= rg = RandomGenerator(seed) r = rg.random() - self.assertEqual(axelrod.match.sample_length(prob_end, r), expected_length) + self.assertEqual(axl.match.sample_length(prob_end, r), expected_length) def test_sample_with_0_prob(self): - self.assertEqual(axelrod.match.sample_length(0, 0.4), float("inf")) + self.assertEqual(axl.match.sample_length(0, 0.4), float("inf")) def test_sample_with_1_prob(self): - self.assertEqual(axelrod.match.sample_length(1, 0.6), 1) ->>>>>>> First pass on reproducible matches and parallel tournaments with random seeding + self.assertEqual(axl.match.sample_length(1, 0.6), 1) diff --git a/axelrod/tests/unit/test_match_generator.py b/axelrod/tests/unit/test_match_generator.py index 27faa78c0..ef8de70c8 100644 --- a/axelrod/tests/unit/test_match_generator.py +++ b/axelrod/tests/unit/test_match_generator.py @@ -4,7 +4,7 @@ from axelrod.match_generator import graph_is_connected from hypothesis import example, given, settings -from hypothesis.strategies import floats, integers +from hypothesis.strategies import integers test_strategies = [ axl.Cooperator, @@ -167,12 +167,12 @@ def test_build_match_chunks(self, repetitions): players=self.players, turns=test_turns, game=test_game, - repetitions=repetitions, + repetitions=repetitions ) chunks = list(rr.build_match_chunks()) match_definitions = [ tuple(list(index_pair) + [repetitions]) - for (index_pair, match_params, repetitions) in chunks + for (index_pair, match_params, repetitions, _) in chunks ] expected_match_definitions = [ (i, j, repetitions) for i in range(5) for j in range(i, 5) @@ -180,6 +180,47 @@ def test_build_match_chunks(self, repetitions): self.assertEqual(sorted(match_definitions), sorted(expected_match_definitions)) + @given(repetitions=integers(min_value=1, max_value=test_repetitions), + seed=integers(min_value=1, max_value=4294967295),) + @settings(max_examples=5) + def test_seeding_equality(self, repetitions, seed): + rr1 = axelrod.MatchGenerator( + players=self.players, + turns=test_turns, + game=test_game, + repetitions=repetitions, + seed=seed + ) + chunks1 = list(rr1.build_match_chunks()) + rr2 = axelrod.MatchGenerator( + players=self.players, + turns=test_turns, + game=test_game, + repetitions=repetitions, + seed=seed + ) + chunks2 = list(rr2.build_match_chunks()) + self.assertEqual(chunks1, chunks2) + + def test_seeding_inequality(self, repetitions=10): + rr1 = axelrod.MatchGenerator( + players=self.players, + turns=test_turns, + game=test_game, + repetitions=repetitions, + seed=0 + ) + chunks1 = list(rr1.build_match_chunks()) + rr2 = axelrod.MatchGenerator( + players=self.players, + turns=test_turns, + game=test_game, + repetitions=repetitions, + seed=1 + ) + chunks2 = list(rr2.build_match_chunks()) + self.assertNotEqual(chunks1, chunks2) + @given(repetitions=integers(min_value=1, max_value=test_repetitions)) @settings(max_examples=5) @example(repetitions=test_repetitions) @@ -195,7 +236,7 @@ def test_spatial_build_match_chunks(self, repetitions): chunks = list(rr.build_match_chunks()) match_definitions = [ tuple(list(index_pair) + [repetitions]) - for (index_pair, match_params, repetitions) in chunks + for (index_pair, match_params, repetitions, _) in chunks ] expected_match_definitions = [(i, j, repetitions) for i, j in cycle] diff --git a/axelrod/tests/unit/test_moran.py b/axelrod/tests/unit/test_moran.py index c9b413842..5a48f1171 100644 --- a/axelrod/tests/unit/test_moran.py +++ b/axelrod/tests/unit/test_moran.py @@ -8,6 +8,8 @@ from axelrod.tests.property import strategy_lists from hypothesis import example, given, settings +from hypothesis.strategies import integers + C, D = axl.Action.C, axl.Action.D @@ -62,24 +64,22 @@ def test_set_players(self): def test_mutate(self): """Test that a mutated player is returned""" players = axl.Cooperator(), axl.Defector(), axl.TitForTat() - mp = axl.MoranProcess(players, mutation_rate=0.5) - axl.seed(0) + mp = MoranProcess(players, mutation_rate=0.5, seed=0) self.assertEqual(mp.mutate(0), players[0]) - axl.seed(1) + mp = MoranProcess(players, mutation_rate=0.5, seed=1) self.assertEqual(mp.mutate(0), players[2]) - axl.seed(4) + mp = MoranProcess(players, mutation_rate=0.5, seed=3) self.assertEqual(mp.mutate(0), players[1]) def test_death_in_db(self): players = axl.Cooperator(), axl.Defector(), axl.TitForTat() - mp = axl.MoranProcess(players, mutation_rate=0.5, mode="db") - axl.seed(1) + mp = MoranProcess(players, mutation_rate=0.5, mode="db", seed=1) self.assertEqual(mp.death(), 0) self.assertEqual(mp.dead, 0) - axl.seed(5) + mp = MoranProcess(players, mutation_rate=0.5, mode="db", seed=2) self.assertEqual(mp.death(), 1) self.assertEqual(mp.dead, 1) - axl.seed(2) + mp = MoranProcess(players, mutation_rate=0.5, mode="db", seed=5) self.assertEqual(mp.death(), 2) self.assertEqual(mp.dead, 2) @@ -87,25 +87,22 @@ def test_death_in_bd(self): players = axl.Cooperator(), axl.Defector(), axl.TitForTat() edges = [(0, 1), (2, 0), (1, 2)] graph = axl.graph.Graph(edges, directed=True) - mp = axl.MoranProcess(players, mode="bd", interaction_graph=graph) - axl.seed(1) + mp = MoranProcess(players, mode="bd", interaction_graph=graph, seed=1) self.assertEqual(mp.death(0), 0) - axl.seed(5) + mp = MoranProcess(players, mode="bd", interaction_graph=graph, seed=2) self.assertEqual(mp.death(0), 1) - axl.seed(2) + mp = MoranProcess(players, mode="bd", interaction_graph=graph, seed=3) self.assertEqual(mp.death(0), 0) def test_birth_in_db(self): players = axl.Cooperator(), axl.Defector(), axl.TitForTat() - mp = axl.MoranProcess(players, mode="db") - axl.seed(1) + mp = MoranProcess(players, mode="db", seed=1) self.assertEqual(mp.death(), 0) self.assertEqual(mp.birth(0), 2) def test_birth_in_bd(self): players = axl.Cooperator(), axl.Defector(), axl.TitForTat() - mp = axl.MoranProcess(players, mode="bd") - axl.seed(1) + mp = MoranProcess(players, mode="bd", seed=1) self.assertEqual(mp.birth(), 0) def test_fixation_check(self): @@ -132,11 +129,12 @@ def test_matchup_indices(self): mp = axl.MoranProcess(players, mode="bd", interaction_graph=graph) self.assertEqual(mp._matchup_indices(), {(0, 1), (1, 2), (2, 0)}) - # def test_fps(self): - # self.assertEqual(fitness_proportionate_selection([0, 0, 1]), 2) - # axelrod.seed(1) - # self.assertEqual(fitness_proportionate_selection([1, 1, 1]), 0) - # self.assertEqual(fitness_proportionate_selection([1, 1, 1]), 2) + def test_fps(self): + players = axl.Cooperator(), axl.Defector() + mp = MoranProcess(players, seed=1) + self.assertEqual(mp.fitness_proportionate_selection([0, 0, 1]), 2) + self.assertEqual(mp.fitness_proportionate_selection([1, 1, 1]), 0) + self.assertEqual(mp.fitness_proportionate_selection([1, 1, 1]), 2) def test_exit_condition(self): p1, p2 = axl.Cooperator(), axl.Cooperator() @@ -144,6 +142,24 @@ def test_exit_condition(self): mp.play() self.assertEqual(len(mp), 1) + @given(seed=integers(min_value=1, max_value=4294967295)) + @settings(max_examples=5, deadline=None) + def test_seeding_equality(self, seed): + players = [axl.Random(x) for x in (0.2, 0.4, 0.6, 0.8)] + mp1 = MoranProcess(players, seed=seed) + mp1.play() + mp2 = MoranProcess(players, seed=seed) + mp2.play() + self.assertEqual(mp1.populations, mp2.populations) + + def test_seeding_inequality(self): + players = [axl.Random(x) for x in (0.2, 0.4, 0.6, 0.8)] + mp1 = MoranProcess(players, seed=0) + mp1.play() + mp2 = MoranProcess(players, seed=1) + mp2.play() + self.assertNotEqual(mp1, mp2) + def test_two_players(self): p1, p2 = axl.Cooperator(), axl.Defector() mp = MoranProcess((p1, p2), seed=99) @@ -265,8 +281,7 @@ def test_three_players_with_mutation(self): def test_four_players(self): players = [axl.Cooperator() for _ in range(3)] players.append(axl.Defector()) - axl.seed(29) - mp = axl.MoranProcess(players) + mp = MoranProcess(players, seed=29) populations = mp.play() self.assertEqual(len(mp), 9) self.assertEqual(len(populations), 9) @@ -274,8 +289,7 @@ def test_four_players(self): self.assertEqual(mp.winning_strategy_name, str(axl.Defector())) @given(strategies=strategy_lists(min_size=2, max_size=4)) - @settings(max_examples=5) - + @settings(max_examples=5, deadline=None) # Two specific examples relating to cloning of strategies @example(strategies=[axl.BackStabber, axl.MindReader]) @example(strategies=[axl.ThueMorse, axl.MindReader]) @@ -289,8 +303,7 @@ def test_property_players(self, strategies): def test_reset(self): p1, p2 = axl.Cooperator(), axl.Defector() - axl.seed(45) - mp = axl.MoranProcess((p1, p2)) + mp = MoranProcess((p1, p2), seed=45) mp.play() self.assertEqual(len(mp), 4) self.assertEqual(len(mp.score_history), 3) @@ -304,14 +317,13 @@ def test_reset(self): def test_constant_fitness_case(self): # Scores between an Alternator and Defector will be: (1, 6) - axl.seed(0) players = ( axl.Alternator(), axl.Alternator(), axl.Defector(), axl.Defector(), ) - mp = axl.MoranProcess(players, turns=2) + mp = MoranProcess(players, turns=2, seed=0) winners = [] for _ in range(100): mp.play() @@ -338,9 +350,9 @@ def test_iter(self): def test_population_plot(self): # Test that can plot on a given matplotlib axes - axl.seed(15) - players = [random.choice(axl.demo_strategies)() for _ in range(5)] - mp = axl.MoranProcess(players=players, turns=30) + rng = axl.RandomGenerator(seed=15) + players = [rng.choice(axl.demo_strategies)() for _ in range(5)] + mp = axl.MoranProcess(players=players, turns=30, seed=20) mp.play() fig, axarr = plt.subplots(2, 2) ax = axarr[1, 0] @@ -458,15 +470,15 @@ def test_asymmetry(self): for _ in range(N // 2): players.append(axl.Defector()) for seed, outcome in seeds: - axl.seed(seed) - mp = axl.MoranProcess( - players, interaction_graph=graph1, reproduction_graph=graph2 + mp = MoranProcess( + players, interaction_graph=graph1, reproduction_graph=graph2, + seed=seed ) mp.play() winner = mp.winning_strategy_name - axl.seed(seed) - mp = axl.MoranProcess( - players, interaction_graph=graph2, reproduction_graph=graph1 + mp = MoranProcess( + players, interaction_graph=graph2, reproduction_graph=graph1, + seed=seed ) mp.play() winner2 = mp.winning_strategy_name @@ -484,12 +496,10 @@ def test_cycle_death_birth(self): for _ in range(N // 2): players.append(axl.Defector()) for seed, outcome in seeds: - axl.seed(seed) - mp = axl.MoranProcess(players, interaction_graph=graph, mode="bd") + mp = MoranProcess(players, interaction_graph=graph, mode="bd", seed=seed) mp.play() winner = mp.winning_strategy_name - axl.seed(seed) - mp = axl.MoranProcess(players, interaction_graph=graph, mode="db") + mp = MoranProcess(players, interaction_graph=graph, mode="db", seed=seed) mp.play() winner2 = mp.winning_strategy_name self.assertEqual((winner == winner2), outcome) diff --git a/axelrod/tests/unit/test_random_.py b/axelrod/tests/unit/test_random_.py index 2e7a7560a..68b72728f 100644 --- a/axelrod/tests/unit/test_random_.py +++ b/axelrod/tests/unit/test_random_.py @@ -1,19 +1,12 @@ """Tests for the random functions.""" -<<<<<<< HEAD - -======= ->>>>>>> First pass on reproducible matches and parallel tournaments with random seeding import unittest import random from collections import Counter -<<<<<<< HEAD import numpy -======= from axelrod import Action, BulkRandomGenerator, Pdf, RandomGenerator ->>>>>>> First pass on reproducible matches and parallel tournaments with random seeding import axelrod as axl @@ -22,27 +15,6 @@ class TestRandomGenerator(unittest.TestCase): def test_return_values(self): -<<<<<<< HEAD - self.assertEqual(axl.random_choice(1), C) - self.assertEqual(axl.random_choice(0), D) - axl.seed(1) - self.assertEqual(axl.random_choice(), C) - axl.seed(2) - self.assertEqual(axl.random_choice(), D) - - def test_set_seed(self): - """Test that numpy and stdlib random seed is set by axelrod seed""" - - numpy_random_numbers = [] - stdlib_random_numbers = [] - for _ in range(2): - axl.seed(0) - numpy_random_numbers.append(numpy.random.random()) - stdlib_random_numbers.append(random.random()) - - self.assertEqual(numpy_random_numbers[0], numpy_random_numbers[1]) - self.assertEqual(stdlib_random_numbers[0], stdlib_random_numbers[1]) -======= random = RandomGenerator() self.assertEqual(random.random_choice(1), C) self.assertEqual(random.random_choice(0), D) @@ -50,28 +22,12 @@ def test_set_seed(self): self.assertEqual(random.random_choice(), C) random.seed(2) self.assertEqual(random.random_choice(), D) ->>>>>>> First pass on reproducible matches and parallel tournaments with random seeding def test_seed_not_offset_by_deterministic_call(self): """Test that when called with p = 0 or 1, the random seed is not affected.""" random = RandomGenerator() for p in [0, 1]: -<<<<<<< HEAD - axl.seed(0) - r = random.random() - axl.seed(0) - axl.random_choice(p) - self.assertEqual(r, random.random()) - - def test_random_flip(self): - self.assertEqual(C, axl.random_flip(C, 0)) - self.assertEqual(C, axl.random_flip(D, 1)) - axl.seed(0) - self.assertEqual(C, axl.random_flip(C, 0.2)) - axl.seed(1) - self.assertEqual(C, axl.random_flip(D, 0.2)) -======= random.seed(0) r = random.random() random.seed(0) @@ -112,7 +68,6 @@ def test_generator(self): randoms3 = [next(rg3) for _ in range(batches * batch_size)] self.assertEqual(len(randoms3), len(randoms2)) self.assertNotIn(randoms3[-1], randoms2) ->>>>>>> First pass on reproducible matches and parallel tournaments with random seeding class TestPdf(unittest.TestCase): @@ -131,13 +86,8 @@ def test_init(self): def test_sample(self): """Test that sample maps to correct domain""" all_samples = [] -<<<<<<< HEAD - - axl.seed(0) -======= random = RandomGenerator() random.seed(0) ->>>>>>> First pass on reproducible matches and parallel tournaments with random seeding for sample in range(100): all_samples.append(self.pdf.sample()) @@ -148,14 +98,7 @@ def test_seed(self): """Test that numpy seeds the sample properly""" for s in range(10): -<<<<<<< HEAD - axl.seed(s) - sample = self.pdf.sample() - axl.seed(s) - self.assertEqual(sample, self.pdf.sample()) -======= pdf1 = Pdf(self.counter, s) sample = pdf1.sample() pdf2 = Pdf(self.counter, s) self.assertEqual(sample, pdf2.sample()) ->>>>>>> First pass on reproducible matches and parallel tournaments with random seeding diff --git a/axelrod/tests/unit/test_resultset.py b/axelrod/tests/unit/test_resultset.py index 8bd0be3af..27b71a79f 100644 --- a/axelrod/tests/unit/test_resultset.py +++ b/axelrod/tests/unit/test_resultset.py @@ -1,6 +1,7 @@ import unittest import csv from collections import Counter + import pandas as pd from dask.dataframe.core import DataFrame from numpy import mean, nanmedian, std @@ -9,7 +10,7 @@ import axelrod as axl from axelrod.load_data_ import axl_filename from axelrod.result_set import create_counter_dict -from axelrod.tests.property import prob_end_tournaments, tournaments +from axelrod.tests.property import tournaments from hypothesis import given, settings @@ -495,9 +496,8 @@ def test_self_interaction_for_random_strategies(self): # Based on https://github.com/Axelrod-Python/Axelrod/issues/670 # Note that the conclusion of #670 is incorrect and only includes one of # the copies of the strategy. - axl.seed(0) players = [s() for s in axl.demo_strategies] - tournament = axl.Tournament(players, repetitions=2, turns=5) + tournament = axl.Tournament(players, repetitions=2, turns=5, seed=0) results = tournament.play(progress_bar=False) self.assertEqual(results.payoff_diffs_means[-1][-1], 0.0) diff --git a/axelrod/tests/unit/test_strategy_transformers.py b/axelrod/tests/unit/test_strategy_transformers.py index 7b909ab22..5d56d004d 100644 --- a/axelrod/tests/unit/test_strategy_transformers.py +++ b/axelrod/tests/unit/test_strategy_transformers.py @@ -124,10 +124,12 @@ def test_StrategyReBuilder_many_decorators(self): def test_all_strategies(self): # Attempt to transform each strategy to ensure that implementation # choices (like use of super) do not cause issues + opponent = axl.Cooperator() for s in axl.strategies: opponent = axl.Cooperator() player = IdentityTransformer()(s)() - player.play(opponent) + match = axl.Match((player, opponent), turns=3) + match.play() def test_naming(self): """Tests that the player and class names are properly modified.""" @@ -226,8 +228,9 @@ def test_dual_transformer_simple_play_regression_test(self): DualTransformer()(axl.Cooperator) )() - for _ in range(3): - multiple_dual_transformers.play(dual_transformer_not_first) + match = axl.Match((multiple_dual_transformers, dual_transformer_not_first), + turns=3) + match.play() self.assertEqual(multiple_dual_transformers.history, [D, D, D]) self.assertEqual(dual_transformer_not_first.history, [D, D, D]) @@ -254,15 +257,12 @@ def assert_dual_wrapper_correct(self, player_class): p2 = DualTransformer()(player_class)() p3 = axl.CyclerCCD() # Cycles 'CCD' - axl.seed(0) - for _ in range(turns): - p1.play(p3) - + match = axl.Match((p1, p3), turns=3, seed=0) + match.play() p3.reset() - axl.seed(0) - for _ in range(turns): - p2.play(p3) + match = axl.Match((p2, p3), turns=3, seed=0) + match.play() self.assertEqual(p1.history, [x.flip() for x in p2.history]) @@ -273,16 +273,15 @@ def test_jossann_transformer(self): p1 = JossAnnTransformer(probability)(axl.Defector)() self.assertFalse(axl.Classifiers["stochastic"](p1)) p2 = axl.Cooperator() - for _ in range(5): - p1.play(p2) + match = axl.Match((p1, p2), turns=5, seed=0) + match.play() self.assertEqual(p1.history, [C, C, C, C, C]) probability = (0, 1) p1 = JossAnnTransformer(probability)(axl.Cooperator)() self.assertFalse(axl.Classifiers["stochastic"](p1)) - p2 = axl.Cooperator() - for _ in range(5): - p1.play(p2) + match = axl.Match((p1, p2), turns=5, seed=0) + match.play() self.assertEqual(p1.history, [D, D, D, D, D]) probability = (0.3, 0.3) @@ -290,17 +289,17 @@ def test_jossann_transformer(self): self.assertTrue(axl.Classifiers["stochastic"](p1)) p2 = axl.Cycler() - axl.seed(0) - for _ in range(5): - p1.play(p2) + match = axl.Match((p1, p2), turns=5, seed=0) + match.play() self.assertEqual(p1.history, [D, C, C, D, D]) probability = (0.6, 0.6) p1 = JossAnnTransformer(probability)(axl.Cooperator)() self.assertTrue(axl.Classifiers["stochastic"](p1)) p2 = axl.Cooperator() - for _ in range(5): - p1.play(p2) + match = axl.Match((p1, p2), turns=5, seed=0) + match.play() + self.assertEqual(p1.history, [D, C, D, D, C]) probability = (0, 1) @@ -329,18 +328,15 @@ def test_jossann_transformer(self): def test_noisy_transformer(self): """Tests that the noisy transformed does flip some moves.""" - random.seed(5) # Cooperator to Defector p1 = axl.Cooperator() p2 = NoisyTransformer(0.5)(axl.Cooperator)() self.assertTrue(axl.Classifiers["stochastic"](p2)) - for _ in range(10): - p1.play(p2) + match = axl.Match((p1, p2), turns=10, seed=5) + match.play() self.assertEqual(p2.history, [C, C, C, C, C, C, D, D, C, C]) - p2 = NoisyTransformer(0)(axl.Cooperator) - self.assertFalse(axl.Classifiers["stochastic"](p2())) - + def test_noisy_transformatino_stochastic(self): p2 = NoisyTransformer(1)(axl.Cooperator) self.assertFalse(axl.Classifiers["stochastic"](p2())) @@ -355,12 +351,11 @@ def test_noisy_transformer(self): def test_forgiving(self): """Tests that the forgiving transformer flips some defections.""" - random.seed(10) p1 = ForgiverTransformer(0.5)(axl.Alternator)() self.assertTrue(axl.Classifiers["stochastic"](p1)) p2 = axl.Defector() - for _ in range(10): - p1.play(p2) + match = axl.Match((p1, p2), turns=10, seed=10) + match.play() self.assertEqual(p1.history, [C, D, C, C, D, C, C, D, C, D]) p1 = ForgiverTransformer(0)(axl.Alternator)() @@ -375,14 +370,14 @@ def test_initial_transformer(self): self.assertEqual(axl.Classifiers["memory_depth"](p1), 0) p2 = InitialTransformer([D, D])(axl.Cooperator)() self.assertEqual(axl.Classifiers["memory_depth"](p2), 2) - for _ in range(5): - p1.play(p2) + match = axl.Match((p1, p2), turns=5, seed=0) + match.play() self.assertEqual(p2.history, [D, D, C, C, C]) p1 = axl.Cooperator() p2 = InitialTransformer([D, D, C, D])(axl.Cooperator)() - for _ in range(5): - p1.play(p2) + match = axl.Match((p1, p2), turns=5, seed=0) + match.play() self.assertEqual(p2.history, [D, D, C, D, C]) p3 = InitialTransformer([D, D])(axl.Adaptive)() @@ -397,9 +392,10 @@ def test_final_transformer(self): self.assertEqual(axl.Classifiers["memory_depth"](p2), 3) self.assertEqual(axl.Classifiers["makes_use_of"](axl.Cooperator()), set([])) - p2.match_attributes["length"] = 6 - for _ in range(8): - p1.play(p2) + # p2.match_attributes["length"] = 6 + match = axl.Match((p1, p2), turns=8, seed=0) + match.play() + self.assertEqual(p2.history, [C, C, C, D, D, D, C, C]) p3 = FinalTransformer([D, D])(axl.Adaptive)() @@ -409,16 +405,16 @@ def test_final_transformer2(self): """Tests the FinalTransformer when tournament length is not known.""" p1 = axl.Cooperator() p2 = FinalTransformer([D, D])(axl.Cooperator)() - for _ in range(6): - p1.play(p2) + match = axl.Match((p1, p2), turns=6, seed=0) + match.play() self.assertEqual(p2.history, [C, C, C, C, C, C]) def test_history_track(self): """Tests the history tracking transformer.""" p1 = axl.Cooperator() p2 = TrackHistoryTransformer()(axl.Random)() - for _ in range(6): - p1.play(p2) + match = axl.Match((p1, p2), turns=6, seed=0) + match.play() self.assertEqual(p2.history, p2._recorded_history) def test_composition(self): @@ -427,17 +423,15 @@ def test_composition(self): cls2 = FinalTransformer([D, D])(cls1) p1 = cls2() p2 = axl.Cooperator() - p1.match_attributes["length"] = 8 - for _ in range(8): - p1.play(p2) + match = axl.Match((p1, p2), turns=8, seed=0) + match.play() self.assertEqual(p1.history, [D, D, C, C, C, C, D, D]) cls1 = FinalTransformer([D, D])(InitialTransformer([D, D])(axl.Cooperator)) p1 = cls1() p2 = axl.Cooperator() - p1.match_attributes["length"] = 8 - for _ in range(8): - p1.play(p2) + match = axl.Match((p1, p2), turns=8, seed=0) + match.play() self.assertEqual(p1.history, [D, D, C, C, C, C, D, D]) def test_compose_transformers(self): @@ -446,32 +440,31 @@ def test_compose_transformers(self): ) p1 = cls1(axl.Cooperator)() p2 = axl.Cooperator() - p1.match_attributes["length"] = 8 - for _ in range(8): - p1.play(p2) + match = axl.Match((p1, p2), turns=8, seed=0) + match.play() self.assertEqual(p1.history, [D, D, C, C, C, C, D, D]) def test_retailiation(self): """Tests the RetaliateTransformer.""" p1 = RetaliationTransformer(1)(axl.Cooperator)() p2 = axl.Defector() - for _ in range(5): - p1.play(p2) + match = axl.Match((p1, p2), turns=5, seed=0) + match.play() self.assertEqual(p1.history, [C, D, D, D, D]) self.assertEqual(p2.history, [D, D, D, D, D]) p1 = RetaliationTransformer(1)(axl.Cooperator)() p2 = axl.Alternator() - for _ in range(5): - p1.play(p2) + match = axl.Match((p1, p2), turns=5, seed=0) + match.play() self.assertEqual(p1.history, [C, C, D, C, D]) self.assertEqual(p2.history, [C, D, C, D, C]) TwoTitsForTat = RetaliationTransformer(2)(axl.Cooperator) p1 = TwoTitsForTat() p2 = axl.CyclerCCD() - for _ in range(9): - p1.play(p2) + match = axl.Match((p1, p2), turns=9, seed=0) + match.play() self.assertEqual(p1.history, [C, C, C, D, D, C, D, D, C]) self.assertEqual(p2.history, [C, C, D, C, C, D, C, C, D]) @@ -480,21 +473,20 @@ def test_retaliation_until_apology(self): TFT = RetaliateUntilApologyTransformer()(axl.Cooperator) p1 = TFT() p2 = axl.Cooperator() - p1.play(p2) - p1.play(p2) + match = axl.Match((p1, p2), turns=2) + match.play() self.assertEqual(p1.history, [C, C]) p1 = TFT() p2 = axl.Defector() - p1.play(p2) - p1.play(p2) + match = axl.Match((p1, p2), turns=2) + match.play() self.assertEqual(p1.history, [C, D]) - random.seed(12) p1 = TFT() p2 = axl.Random() - for _ in range(5): - p1.play(p2) + match = axl.Match((p1, p2), turns=5, seed=12) + match.play() self.assertEqual(p1.history, [C, C, D, D, C]) def test_apology(self): @@ -502,14 +494,15 @@ def test_apology(self): ApologizingDefector = ApologyTransformer([D], [C])(axl.Defector) p1 = ApologizingDefector() p2 = axl.Cooperator() - for _ in range(5): - p1.play(p2) + match = axl.Match((p1, p2), turns=5) + match.play() self.assertEqual(p1.history, [D, C, D, C, D]) + ApologizingDefector = ApologyTransformer([D, D], [C, C])(axl.Defector) p1 = ApologizingDefector() p2 = axl.Cooperator() - for _ in range(6): - p1.play(p2) + match = axl.Match((p1, p2), turns=6, seed=0) + match.play() self.assertEqual(p1.history, [D, D, C, D, D, C]) def test_mixed(self): @@ -520,8 +513,8 @@ def test_mixed(self): p1 = MD() p2 = axl.Cooperator() - for _ in range(5): - p1.play(p2) + match = axl.Match((p1, p2), turns=5) + match.play() self.assertEqual(p1.history, [C, C, C, C, C]) probability = 0 @@ -530,8 +523,8 @@ def test_mixed(self): p1 = MD() p2 = axl.Cooperator() - for _ in range(5): - p1.play(p2) + match = axl.Match((p1, p2), turns=5) + match.play() self.assertEqual(p1.history, [D, D, D, D, D]) # Decorating with list and distribution @@ -545,8 +538,8 @@ def test_mixed(self): p1 = MD() # Against a cooperator we see that we only cooperate p2 = axl.Cooperator() - for _ in range(5): - p1.play(p2) + match = axl.Match((p1, p2), turns=5) + match.play() self.assertEqual(p1.history, [C, C, C, C, C]) # Decorate a cooperator putting all weight on Defector @@ -558,8 +551,8 @@ def test_mixed(self): p1 = MD() # Against a cooperator we see that we only defect p2 = axl.Cooperator() - for _ in range(5): - p1.play(p2) + match = axl.Match((p1, p2), turns=5) + match.play() self.assertEqual(p1.history, [D, D, D, D, D]) def test_deadlock(self): @@ -567,8 +560,8 @@ def test_deadlock(self): # We can induce a deadlock by alterting TFT to defect first p1 = axl.TitForTat() p2 = InitialTransformer([D])(axl.TitForTat)() - for _ in range(4): - p1.play(p2) + match = axl.Match((p1, p2), turns=4) + match.play() self.assertEqual(p1.history, [C, D, C, D]) self.assertEqual(p2.history, [D, C, D, C]) @@ -576,8 +569,8 @@ def test_deadlock(self): # Mutual cooperation p1 = axl.TitForTat() p2 = DeadlockBreakingTransformer()(InitialTransformer([D])(axl.TitForTat))() - for _ in range(4): - p1.play(p2) + match = axl.Match((p1, p2), turns=45, seed=0) + match.play() self.assertEqual(p1.history, [C, D, C, C]) self.assertEqual(p2.history, [D, C, C, C]) @@ -585,15 +578,15 @@ def test_grudging(self): """Test the GrudgeTransformer.""" p1 = axl.Defector() p2 = GrudgeTransformer(1)(axl.Cooperator)() - for _ in range(4): - p1.play(p2) + match = axl.Match((p1, p2), turns=4, seed=0) + match.play() self.assertEqual(p1.history, [D, D, D, D]) self.assertEqual(p2.history, [C, C, D, D]) p1 = InitialTransformer([C])(axl.Defector)() p2 = GrudgeTransformer(2)(axl.Cooperator)() - for _ in range(8): - p1.play(p2) + match = axl.Match((p1, p2), turns=8, seed=0) + match.play() self.assertEqual(p1.history, [C, D, D, D, D, D, D, D]) self.assertEqual(p2.history, [C, C, C, C, D, D, D, D]) @@ -601,22 +594,22 @@ def test_nice(self): """Tests the NiceTransformer.""" p1 = NiceTransformer()(axl.Defector)() p2 = axl.Defector() - for _ in range(5): - p1.play(p2) + match = axl.Match((p1, p2), turns=5) + match.play() self.assertEqual(p1.history, [C, D, D, D, D]) self.assertEqual(p2.history, [D, D, D, D, D]) p1 = NiceTransformer()(axl.Defector)() p2 = axl.Alternator() - for _ in range(5): - p1.play(p2) + match = axl.Match((p1, p2), turns=5) + match.play() self.assertEqual(p1.history, [C, C, D, D, D]) self.assertEqual(p2.history, [C, D, C, D, C]) p1 = NiceTransformer()(axl.Defector)() p2 = axl.Cooperator() - for _ in range(5): - p1.play(p2) + match = axl.Match((p1, p2), turns=5) + match.play() self.assertEqual(p1.history, [C, C, C, C, C]) self.assertEqual(p2.history, [C, C, C, C, C]) @@ -633,9 +626,10 @@ def test_nilpotency(self): player = PlayerClass() transformed = transformer(transformer(PlayerClass))() clone = third_player.clone() - for i in range(5): - player.play(third_player) - transformed.play(clone) + match = axl.Match((player, third_player), turns=5) + match.play() + match = axl.Match((transformed, clone), turns=5) + match.play() self.assertEqual(player.history, transformed.history) def test_idempotency(self): @@ -661,9 +655,10 @@ def test_idempotency(self): clone = third_player.clone() player = transformer(PlayerClass)() transformed = transformer(transformer(PlayerClass))() - for i in range(5): - player.play(third_player) - transformed.play(clone) + match = axl.Match((player, third_player), turns=5) + match.play() + match = axl.Match((transformed, clone), turns=5) + match.play() self.assertEqual(player.history, transformed.history) def test_implementation(self): @@ -673,15 +668,15 @@ def test_implementation(self): # Difference between Alternator and CyclerCD p1 = axl.Cycler(cycle="CD") p2 = FlipTransformer()(axl.Cycler)(cycle="CD") - for _ in range(5): - p1.play(p2) + match = axl.Match((p1, p2), turns=5) + match.play() self.assertEqual(p1.history, [C, D, C, D, C]) self.assertEqual(p2.history, [D, C, D, C, D]) p1 = axl.Alternator() p2 = FlipTransformer()(axl.Alternator)() - for _ in range(5): - p1.play(p2) + match = axl.Match((p1, p2), turns=5) + match.play() self.assertEqual(p1.history, [C, D, C, D, C]) self.assertEqual(p2.history, [D, D, D, D, D]) diff --git a/axelrod/tests/unit/test_strategy_utils.py b/axelrod/tests/unit/test_strategy_utils.py index fef7e2af6..0dc3101d9 100644 --- a/axelrod/tests/unit/test_strategy_utils.py +++ b/axelrod/tests/unit/test_strategy_utils.py @@ -1,7 +1,9 @@ """Tests for the strategy utils.""" - import unittest +from hypothesis import given, settings +from hypothesis.strategies import integers, lists, sampled_from + import axelrod as axl from axelrod._strategy_utils import ( detect_cycle, @@ -12,9 +14,6 @@ thue_morse_generator, ) -from hypothesis import given, settings -from hypothesis.strategies import integers, lists, sampled_from - C, D = axl.Action.C, axl.Action.D @@ -67,11 +66,13 @@ class TestInspectStrategy(unittest.TestCase): def test_strategies_without_countermeasures_return_their_strategy(self): tft = axl.TitForTat() inspector = axl.Alternator() - - tft.play(inspector) + match = axl.Match((tft, inspector), turns=1) + match.play() self.assertEqual(tft.history, [C]) self.assertEqual(inspect_strategy(inspector=inspector, opponent=tft), C) - tft.play(inspector) + + match = axl.Match((tft, inspector), turns=2) + match.play() self.assertEqual(tft.history, [C, C]) self.assertEqual(inspect_strategy(inspector=inspector, opponent=tft), D) self.assertEqual(tft.strategy(inspector), D) @@ -79,8 +80,8 @@ def test_strategies_without_countermeasures_return_their_strategy(self): def test_strategies_with_countermeasures_return_their_countermeasures(self): d_geller = axl.GellerDefector() inspector = axl.Cooperator() - d_geller.play(inspector) - + match = axl.Match((d_geller, inspector), turns=1) + match.play() self.assertEqual(inspect_strategy(inspector=inspector, opponent=d_geller), D) self.assertEqual(d_geller.strategy(inspector), C) diff --git a/axelrod/tests/unit/test_tournament.py b/axelrod/tests/unit/test_tournament.py index c91abcf46..b8f1266e0 100644 --- a/axelrod/tests/unit/test_tournament.py +++ b/axelrod/tests/unit/test_tournament.py @@ -10,11 +10,13 @@ import warnings from multiprocessing import Queue, cpu_count -from axelrod.load_data_ import axl_filename +from hypothesis import example, given, settings +from hypothesis.strategies import floats, integers import numpy as np import pandas as pd from tqdm import tqdm +from axelrod.load_data_ import axl_filename import axelrod as axl from axelrod.tests.property import ( prob_end_tournaments, @@ -24,8 +26,6 @@ ) from axelrod.tournament import _close_objects -from hypothesis import example, given, settings -from hypothesis.strategies import floats, integers C, D = axl.Action.C, axl.Action.D @@ -399,7 +399,7 @@ def test_progress_bar_play_parallel(self): max_repetitions=4, ) ) - @settings(max_examples=50) + @settings(max_examples=50, deadline=None) @example( tournament=axl.Tournament( players=[s() for s in test_strategies], @@ -657,7 +657,7 @@ def make_chunk_generator(): for player2_index in range(player1_index, len(self.players)): index_pair = (player1_index, player2_index) match_params = {"turns": turns, "game": self.game} - yield (index_pair, match_params, self.test_repetitions) + yield (index_pair, match_params, self.test_repetitions, 0) chunk_generator = make_chunk_generator() interactions = {} @@ -748,6 +748,54 @@ def test_write_to_csv_without_results(self): expected_df = pd.read_csv(axl_filename(path)) self.assertTrue(df.equals(expected_df)) + @given(seed=integers(min_value=1, max_value=4294967295)) + @settings(max_examples=5, deadline=None) + def test_seeding_equality(self, seed): + """Tests that a tournament with a given seed will return the + same results each time.""" + players = [axl.Random(0.4), axl.Random(0.6), axl.Random(0.8)] + tournament1 = axl.Tournament( + name=self.test_name, + players=players, + game=self.game, + turns=3, + repetitions=3, + seed=seed + ) + tournament2 = axl.Tournament( + name=self.test_name, + players=players, + game=self.game, + turns=3, + repetitions=3, + seed=seed + ) + results1 = tournament1.play() + results2 = tournament2.play() + self.assertEqual(results1.summarise(), results2.summarise()) + + def test_seeding_inequality(self): + players = [axl.Random(0.4), axl.Random(0.6)] + tournament1 = axl.Tournament( + name=self.test_name, + players=players, + game=self.game, + turns=2, + repetitions=2, + seed=0 + ) + tournament2 = axl.Tournament( + name=self.test_name, + players=players, + game=self.game, + turns=2, + repetitions=2, + seed=10 + ) + results1 = tournament1.play() + results2 = tournament2.play() + self.assertNotEqual(results1, results2) + class TestProbEndTournament(unittest.TestCase): @classmethod @@ -785,14 +833,16 @@ def test_init(self): max_prob_end=0.9, min_repetitions=2, max_repetitions=4, + seed=100 ) ) - @settings(max_examples=5) + @settings(max_examples=5, deadline=None) @example( tournament=axl.Tournament( players=[s() for s in test_strategies], prob_end=0.2, repetitions=test_repetitions, + seed=101 ) ) # These two examples are to make sure #465 is fixed. @@ -800,12 +850,18 @@ def test_init(self): # these two examples were identified by hypothesis. @example( tournament=axl.Tournament( - players=[axl.BackStabber(), axl.MindReader()], prob_end=0.2, repetitions=1, + players=[axl.BackStabber(), axl.MindReader()], + prob_end=0.2, + repetitions=1, + seed=102 ) ) @example( tournament=axl.Tournament( - players=[axl.ThueMorse(), axl.MindReader()], prob_end=0.2, repetitions=1, + players=[axl.ThueMorse(), axl.MindReader()], + prob_end=0.2, + repetitions=1, + seed=103 ) ) def test_property_serial_play(self, tournament): @@ -857,7 +913,7 @@ def test_init(self): noise=floats(min_value=0, max_value=1), seed=integers(min_value=0, max_value=4294967295), ) - @settings(max_examples=5) + @settings(max_examples=5, deadline=None) def test_complete_tournament(self, strategies, turns, repetitions, noise, seed): """ A test to check that a spatial tournament on the complete multigraph @@ -873,16 +929,16 @@ def test_complete_tournament(self, strategies, turns, repetitions, noise, seed): # create a round robin tournament tournament = axl.Tournament( - players, repetitions=repetitions, turns=turns, noise=noise + players, repetitions=repetitions, turns=turns, noise=noise, + seed=seed ) # create a complete spatial tournament spatial_tournament = axl.Tournament( - players, repetitions=repetitions, turns=turns, noise=noise, edges=edges + players, repetitions=repetitions, turns=turns, noise=noise, edges=edges, + seed=seed ) - axl.seed(seed) results = tournament.play(progress_bar=False) - axl.seed(seed) spatial_results = spatial_tournament.play(progress_bar=False) self.assertEqual(results.ranked_names, spatial_results.ranked_names) @@ -964,7 +1020,7 @@ def test_init(self): reps=integers(min_value=1, max_value=3), seed=integers(min_value=0, max_value=4294967295), ) - @settings(max_examples=5) + @settings(max_examples=5, deadline=None) def test_complete_tournament(self, strategies, prob_end, seed, reps): """ A test to check that a spatial tournament on the complete graph @@ -973,8 +1029,9 @@ def test_complete_tournament(self, strategies, prob_end, seed, reps): players = [s() for s in strategies] # create a prob end round robin tournament - tournament = axl.Tournament(players, prob_end=prob_end, repetitions=reps) - axl.seed(seed) + + tournament = axl.Tournament(players, prob_end=prob_end, repetitions=reps, + seed=seed) results = tournament.play(progress_bar=False) # create a complete spatial tournament @@ -982,9 +1039,9 @@ def test_complete_tournament(self, strategies, prob_end, seed, reps): edges = [(i, j) for i in range(len(players)) for j in range(i, len(players))] spatial_tournament = axl.Tournament( - players, prob_end=prob_end, repetitions=reps, edges=edges + players, prob_end=prob_end, repetitions=reps, edges=edges, + seed=seed ) - axl.seed(seed) spatial_results = spatial_tournament.play(progress_bar=False) self.assertEqual(results.match_lengths, spatial_results.match_lengths) self.assertEqual(results.ranked_names, spatial_results.ranked_names) @@ -1001,7 +1058,7 @@ def test_complete_tournament(self, strategies, prob_end, seed, reps): ), seed=integers(min_value=0, max_value=4294967295), ) - @settings(max_examples=5) + @settings(max_examples=5, deadline=None) def test_one_turn_tournament(self, tournament, seed): """ Tests that gives same result as the corresponding spatial round robin @@ -1012,10 +1069,9 @@ def test_one_turn_tournament(self, tournament, seed): prob_end=1, edges=tournament.edges, repetitions=tournament.repetitions, + seed=seed, ) - axl.seed(seed) prob_end_results = prob_end_tour.play(progress_bar=False) - axl.seed(seed) one_turn_results = tournament.play(progress_bar=False) self.assertEqual(prob_end_results.scores, one_turn_results.scores) self.assertEqual(prob_end_results.wins, one_turn_results.wins) diff --git a/axelrod/tournament.py b/axelrod/tournament.py index 6b96edbcc..7e8f666ce 100644 --- a/axelrod/tournament.py +++ b/axelrod/tournament.py @@ -33,6 +33,7 @@ def __init__( noise: float = 0, edges: List[Tuple] = None, match_attributes: dict = None, + seed: int = None ) -> None: """ Parameters @@ -85,6 +86,7 @@ def __init__( noise=self.noise, edges=edges, match_attributes=match_attributes, + seed=seed ) self._logger = logging.getLogger(__name__) diff --git a/requirements.txt b/requirements.txt index 5c0d22cab..b9e70133f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ cloudpickle>=0.2.1 fsspec>=0.4.3 dask>=2.3.0 +hypothesis>=4.0 matplotlib>=2.0.0 numpy>=1.9.2 pandas>=0.18.1 From 59f156dadfbf842fa4745e617abd56532fcaa475 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sun, 1 Mar 2020 21:23:30 -0800 Subject: [PATCH 004/121] Update tournament test to only compare ranked_names --- axelrod/tests/unit/test_tournament.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/axelrod/tests/unit/test_tournament.py b/axelrod/tests/unit/test_tournament.py index b8f1266e0..907bd451d 100644 --- a/axelrod/tests/unit/test_tournament.py +++ b/axelrod/tests/unit/test_tournament.py @@ -753,13 +753,14 @@ def test_write_to_csv_without_results(self): def test_seeding_equality(self, seed): """Tests that a tournament with a given seed will return the same results each time.""" - players = [axl.Random(0.4), axl.Random(0.6), axl.Random(0.8)] + rng = axl.RandomGenerator(seed=seed) + players = [axl.Random(rng.random()) for _ in range(8)] tournament1 = axl.Tournament( name=self.test_name, players=players, game=self.game, turns=3, - repetitions=3, + repetitions=100, seed=seed ) tournament2 = axl.Tournament( @@ -767,12 +768,12 @@ def test_seeding_equality(self, seed): players=players, game=self.game, turns=3, - repetitions=3, + repetitions=100, seed=seed ) - results1 = tournament1.play() - results2 = tournament2.play() - self.assertEqual(results1.summarise(), results2.summarise()) + results1 = tournament1.play(processes=2) + results2 = tournament2.play(processes=2) + self.assertEqual(results1.ranked_names, results2.ranked_names) def test_seeding_inequality(self): players = [axl.Random(0.4), axl.Random(0.6)] From 06255f32612f28879e174d2bdfc8418e321c0b4b Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sun, 1 Mar 2020 21:35:53 -0800 Subject: [PATCH 005/121] Undo or remove various commented lines --- axelrod/evolvable_player.py | 18 -------- axelrod/match.py | 2 +- axelrod/player.py | 17 -------- axelrod/random_.py | 1 - axelrod/tests/strategies/test_geller.py | 6 +-- axelrod/tests/strategies/test_titfortat.py | 50 +++++++++++----------- 6 files changed, 29 insertions(+), 65 deletions(-) diff --git a/axelrod/evolvable_player.py b/axelrod/evolvable_player.py index 0f3e370eb..c3898b734 100644 --- a/axelrod/evolvable_player.py +++ b/axelrod/evolvable_player.py @@ -36,24 +36,6 @@ def create_new(self, **kwargs): init_kwargs.update(kwargs) return self.__class__(**init_kwargs) - # def clone(self): - # """Clones the player without history, reapplying configuration - # parameters as necessary. - # - # We're overriding Player.clone to also propagate the seed because EvolvablePlayers - # currently randomize themselves on initialization. - # """ - # - # # You may be tempted to re-implement using the `copy` module - # # Note that this would require a deepcopy in some cases and there may - # # be significant changes required throughout the library. - # # Consider overriding in special cases only if necessary - # cls = self.__class__ - # new_player = cls(**self.init_kwargs, seed=self._seed) - # new_player.match_attributes = copy.copy(self.match_attributes) - # # new_player.set_seed(self._seed) - # return new_player - # Serialization and deserialization. You may overwrite to obtain more human readable serializations # but you must overwrite both. diff --git a/axelrod/match.py b/axelrod/match.py index 4e7b5b4e4..8b16ac52c 100644 --- a/axelrod/match.py +++ b/axelrod/match.py @@ -178,9 +178,9 @@ def play(self): if self.reset: p.reset() p.set_match_attributes(**self.match_attributes) - # if p.classifier["stochastic"]: # Generate a random seed for the player # TODO: Seeds only for stochastic players + # if p.classifier["stochastic"]: p.set_seed(self._random.random_seed_int()) result = [] for _ in range(turns): diff --git a/axelrod/player.py b/axelrod/player.py index bf060cadf..254f5d6c2 100644 --- a/axelrod/player.py +++ b/axelrod/player.py @@ -28,18 +28,6 @@ def __new__(cls, *args, **kwargs): """Caches arguments for Player cloning.""" obj = super().__new__(cls) obj.init_kwargs = cls.init_params(*args, **kwargs) - # # Set up random seed from the module level random seed - # # in case the user doesn't specific one later. - # need_seed = False - # try: - # seed = kwargs["seed"] - # if seed is None: - # need_seed = True - # except KeyError: - # need_seed = True - # if need_seed: - # seed = _module_random.randint(0, 2**32-1) - # obj._seed = seed return obj @classmethod @@ -163,10 +151,6 @@ def strategy(self, opponent): """This is a placeholder strategy.""" raise NotImplementedError() - # def play(self, opponent, noise=0): - # """This pits two players against each other.""" - # return simultaneous_play(self, opponent, noise) - def clone(self): """Clones the player without history, reapplying configuration parameters as necessary.""" @@ -190,7 +174,6 @@ def reset(self): """ # This also resets the history. self.__init__(**self.init_kwargs) - # self.set_seed(self._seed) def update_history(self, play, coplay): self.history.append(play, coplay) diff --git a/axelrod/random_.py b/axelrod/random_.py index b059803df..2151761b9 100644 --- a/axelrod/random_.py +++ b/axelrod/random_.py @@ -11,7 +11,6 @@ class RandomGenerator(object): Enables reproducibility of player behavior, matches, and tournaments.""" def __init__(self, seed=None): - # self.random = random.Random() # _random is the internal object that generators random values self._random = RandomState() self.original_seed = seed diff --git a/axelrod/tests/strategies/test_geller.py b/axelrod/tests/strategies/test_geller.py index d9e5f9d2c..f201e1815 100644 --- a/axelrod/tests/strategies/test_geller.py +++ b/axelrod/tests/strategies/test_geller.py @@ -66,9 +66,9 @@ def test_returns_foil_inspection_strategy_of_opponent(self): self.versus_test(axl.Darwin(), expected_actions=[(C, C), (C, C), (C, C)], seed=3) - # self.versus_test( - # axelrod.MindReader(), expected_actions=[(D, D), (D, D), (D, D)], seed=1 - # ) + self.versus_test( + axelrod.MindReader(), expected_actions=[(D, D), (D, D), (D, D)], seed=1 + ) class TestGellerCooperator(TestGeller): diff --git a/axelrod/tests/strategies/test_titfortat.py b/axelrod/tests/strategies/test_titfortat.py index a0a795837..43b631c6f 100644 --- a/axelrod/tests/strategies/test_titfortat.py +++ b/axelrod/tests/strategies/test_titfortat.py @@ -708,9 +708,9 @@ class TestContriteTitForTat(TestPlayer): ] def test_init(self): - ctft = self.player() - self.assertFalse(ctft.contrite, False) - self.assertEqual(ctft._recorded_history, []) + player = self.player() + self.assertFalse(player.contrite, False) + self.assertEqual(player._recorded_history, []) @given( strategies=strategy_lists(strategies=deterministic_strategies, max_size=1), @@ -719,10 +719,10 @@ def test_init(self): @settings(deadline=None) def test_is_tit_for_tat_with_no_noise(self, strategies, turns): tft = axl.TitForTat() - ctft = self.player() + player = self.player() opponent = strategies[0]() m1 = axl.Match((tft, opponent), turns) - m2 = axl.Match((ctft, opponent), turns) + m2 = axl.Match((player, opponent), turns) self.assertEqual(m1.play(), m2.play()) def test_strategy_with_noise(self): @@ -739,26 +739,26 @@ def test_strategy_with_noise(self): self.assertEqual(player._recorded_history, [C]) self.assertEqual(opponent.history, [C]) - # # After noise: is contrite - # ctft.play(opponent) - # self.assertEqual(ctft.history, [D, C]) - # self.assertEqual(ctft._recorded_history, [C, C]) - # self.assertEqual(opponent.history, [C, D]) - # self.assertTrue(ctft.contrite) - # - # # Cooperates and no longer contrite - # ctft.play(opponent) - # self.assertEqual(ctft.history, [D, C, C]) - # self.assertEqual(ctft._recorded_history, [C, C, C]) - # self.assertEqual(opponent.history, [C, D, D]) - # self.assertFalse(ctft.contrite) - # - # # Goes back to playing tft - # ctft.play(opponent) - # self.assertEqual(ctft.history, [D, C, C, D]) - # self.assertEqual(ctft._recorded_history, [C, C, C, D]) - # self.assertEqual(opponent.history, [C, D, D, D]) - # self.assertFalse(ctft.contrite) + # After noise: is contrite + player.play(opponent) + self.assertEqual(player.history, [D, C]) + self.assertEqual(player._recorded_history, [C, C]) + self.assertEqual(opponent.history, [C, D]) + self.assertTrue(player.contrite) + + # Cooperates and no longer contrite + player.play(opponent) + self.assertEqual(player.history, [D, C, C]) + self.assertEqual(player._recorded_history, [C, C, C]) + self.assertEqual(opponent.history, [C, D, D]) + self.assertFalse(player.contrite) + + # Goes back to playing tft + player.play(opponent) + self.assertEqual(player.history, [D, C, C, D]) + self.assertEqual(player._recorded_history, [C, C, C, D]) + self.assertEqual(opponent.history, [C, D, D, D]) + self.assertFalse(player.contrite) class TestAdaptiveTitForTat(TestPlayer): From bad199ef850c1c6540367d026e9d889d088acb76 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sun, 1 Mar 2020 21:37:39 -0800 Subject: [PATCH 006/121] Remove deadline for filtering test --- axelrod/tests/integration/test_filtering.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/axelrod/tests/integration/test_filtering.py b/axelrod/tests/integration/test_filtering.py index 57406484b..10c1cd96d 100644 --- a/axelrod/tests/integration/test_filtering.py +++ b/axelrod/tests/integration/test_filtering.py @@ -93,11 +93,10 @@ def test_memory_depth_filtering( filtered = set(axl.filtered_strategies(filterset, strategies=strategies)) self.assertEqual(comprehension, filtered) - @given( - seed_=integers(min_value=0, max_value=4294967295), - strategies=strategy_lists(min_size=20, max_size=20), - ) - @settings(max_examples=5) + @given(seed_=integers(min_value=0, max_value=4294967295), + strategies=strategy_lists(min_size=20, max_size=20), + ) + @settings(max_examples=5, deadline=None) def test_makes_use_of_filtering(self, seed_, strategies): """ Test equivalent filtering using two approaches. @@ -107,7 +106,7 @@ def test_makes_use_of_filtering(self, seed_, strategies): classifiers = [["game"], ["length"], ["game", "length"]] for classifier in classifiers: - axelrod._module_random.seed(seed_) + axl._module_random.seed(seed_) comprehension = set( [ s @@ -116,7 +115,7 @@ def test_makes_use_of_filtering(self, seed_, strategies): ] ) - axelrod._module_random.seed(seed_) + axl._module_random.seed(seed_) filterset = {"makes_use_of": classifier} filtered = set(axl.filtered_strategies(filterset, strategies=strategies)) From 423b59f9bb0eb9dd35f1fbb0717363a386735236 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Mon, 20 Apr 2020 19:30:39 -0700 Subject: [PATCH 007/121] Cleanup post rebase, update some imports --- axelrod/evolvable_player.py | 1 - axelrod/match.py | 2 +- axelrod/moran.py | 8 ++--- axelrod/plot.py | 4 +-- axelrod/result_set.py | 5 ++- axelrod/tests/integration/test_filtering.py | 1 - .../tests/strategies/test_axelrod_second.py | 8 +---- axelrod/tests/strategies/test_cycler.py | 2 +- .../tests/strategies/test_evolvable_player.py | 1 - .../strategies/test_finite_state_machines.py | 5 +-- axelrod/tests/strategies/test_gambler.py | 7 ++-- axelrod/tests/strategies/test_lookerup.py | 7 ++-- axelrod/tests/strategies/test_mindreader.py | 2 +- axelrod/tests/strategies/test_qlearner.py | 3 -- axelrod/tests/strategies/test_retaliate.py | 2 +- axelrod/tests/strategies/test_selfsteem.py | 2 -- .../tests/unit/test_deterministic_cache.py | 2 +- axelrod/tests/unit/test_eigen.py | 33 +++++++++---------- axelrod/tests/unit/test_fingerprint.py | 11 +++---- axelrod/tests/unit/test_graph.py | 3 +- axelrod/tests/unit/test_history.py | 3 +- axelrod/tests/unit/test_interaction_utils.py | 4 +-- axelrod/tests/unit/test_match.py | 6 ++-- axelrod/tests/unit/test_moran.py | 8 +++-- axelrod/tests/unit/test_pickling.py | 5 ++- axelrod/tests/unit/test_plot.py | 5 ++- axelrod/tests/unit/test_random_.py | 10 ++---- axelrod/tests/unit/test_resultset.py | 9 +++-- axelrod/tests/unit/test_tournament.py | 7 ++-- axelrod/tournament.py | 3 +- 30 files changed, 68 insertions(+), 101 deletions(-) diff --git a/axelrod/evolvable_player.py b/axelrod/evolvable_player.py index c3898b734..864f8746c 100644 --- a/axelrod/evolvable_player.py +++ b/axelrod/evolvable_player.py @@ -1,5 +1,4 @@ import base64 -import copy from pickle import dumps, loads from typing import Dict, List from .player import Player diff --git a/axelrod/match.py b/axelrod/match.py index 8b16ac52c..f507a06cf 100644 --- a/axelrod/match.py +++ b/axelrod/match.py @@ -6,7 +6,7 @@ from axelrod import Classifiers from axelrod.game import Game from axelrod.random_ import RandomGenerator -from .deterministic_cache import DeterministicCache +from axelrod.deterministic_cache import DeterministicCache C, D = Action.C, Action.D diff --git a/axelrod/moran.py b/axelrod/moran.py index faca0701b..fd099a97f 100644 --- a/axelrod/moran.py +++ b/axelrod/moran.py @@ -7,10 +7,10 @@ import numpy as np from axelrod import EvolvablePlayer, DEFAULT_TURNS, Game, Player -from .deterministic_cache import DeterministicCache -from .graph import Graph, complete_graph -from .match import Match -from .random_ import RandomGenerator, BulkRandomGenerator +from axelrod.deterministic_cache import DeterministicCache +from axelrod.graph import Graph, complete_graph +from axelrod.match import Match +from axelrod.random_ import RandomGenerator, BulkRandomGenerator class MoranProcess(object): diff --git a/axelrod/plot.py b/axelrod/plot.py index edc596529..7782fa104 100644 --- a/axelrod/plot.py +++ b/axelrod/plot.py @@ -1,12 +1,12 @@ from distutils.version import LooseVersion +import pathlib from typing import List, Union import matplotlib import matplotlib.pyplot as plt import matplotlib.transforms as transforms -import pathlib -import tqdm from numpy import arange, median, nan_to_num +import tqdm from .result_set import ResultSet from .load_data_ import axl_filename diff --git a/axelrod/result_set.py b/axelrod/result_set.py index bffbd283e..69bf1b97f 100644 --- a/axelrod/result_set.py +++ b/axelrod/result_set.py @@ -5,11 +5,10 @@ from typing import List import warnings -import numpy as np -import tqdm - import dask as da import dask.dataframe as dd +import numpy as np +import tqdm from axelrod.action import Action from . import eigen diff --git a/axelrod/tests/integration/test_filtering.py b/axelrod/tests/integration/test_filtering.py index 10c1cd96d..d53792d0f 100644 --- a/axelrod/tests/integration/test_filtering.py +++ b/axelrod/tests/integration/test_filtering.py @@ -2,7 +2,6 @@ import warnings import axelrod as axl -from axelrod import filtered_strategies, short_run_time_strategies from axelrod.tests.property import strategy_lists from hypothesis import example, given, settings diff --git a/axelrod/tests/strategies/test_axelrod_second.py b/axelrod/tests/strategies/test_axelrod_second.py index 7c9122d8d..64c67c123 100644 --- a/axelrod/tests/strategies/test_axelrod_second.py +++ b/axelrod/tests/strategies/test_axelrod_second.py @@ -1,11 +1,6 @@ """Tests for the Second Axelrod strategies.""" -import random - import axelrod as axl - -import numpy as np - from .test_player import TestPlayer C, D = axl.Action.C, axl.Action.D @@ -1155,9 +1150,8 @@ def test_strategy(self): ] # Enter defect mode. expected_actions += [(D, C)] - random.seed(10) player = self.player() - match = axl.Match((player, axl.Random()), turns=len(expected_actions)) + match = axl.Match((player, axl.Random()), turns=len(expected_actions), seed=10) # The history matrix will be [[0, 2], [5, 6], [3, 6], [4, 2]] actions = match.play() self.assertEqual(actions, expected_actions) diff --git a/axelrod/tests/strategies/test_cycler.py b/axelrod/tests/strategies/test_cycler.py index 7753a9712..126cd3709 100644 --- a/axelrod/tests/strategies/test_cycler.py +++ b/axelrod/tests/strategies/test_cycler.py @@ -1,7 +1,6 @@ """Tests for the Cycler strategies.""" import unittest import itertools -import random import axelrod as axl from axelrod._strategy_utils import detect_cycle @@ -12,6 +11,7 @@ from .test_evolvable_player import PartialClass, TestEvolvablePlayer C, D = Action.C, Action.D +random = axl.RandomGenerator() class TestAntiCycler(TestPlayer): diff --git a/axelrod/tests/strategies/test_evolvable_player.py b/axelrod/tests/strategies/test_evolvable_player.py index 6370987cf..d93e0abfd 100644 --- a/axelrod/tests/strategies/test_evolvable_player.py +++ b/axelrod/tests/strategies/test_evolvable_player.py @@ -1,4 +1,3 @@ -import unittest import functools import unittest diff --git a/axelrod/tests/strategies/test_finite_state_machines.py b/axelrod/tests/strategies/test_finite_state_machines.py index ccefa14f8..d2bed89e3 100644 --- a/axelrod/tests/strategies/test_finite_state_machines.py +++ b/axelrod/tests/strategies/test_finite_state_machines.py @@ -1,9 +1,6 @@ """Tests for Finite State Machine Strategies.""" - import unittest -import random - import axelrod as axl from axelrod.compute_finite_state_machine_memory import get_memory_from_transitions from axelrod.evolvable_player import InsufficientParametersError @@ -13,7 +10,7 @@ from .test_evolvable_player import PartialClass, TestEvolvablePlayer C, D = axl.Action.C, axl.Action.D - +random = axl.RandomGenerator() class TestSimpleFSM(unittest.TestCase): def setUp(self): diff --git a/axelrod/tests/strategies/test_gambler.py b/axelrod/tests/strategies/test_gambler.py index 571189d38..a6086eb8a 100755 --- a/axelrod/tests/strategies/test_gambler.py +++ b/axelrod/tests/strategies/test_gambler.py @@ -1,10 +1,7 @@ """Test for the Gambler strategy. Most tests come from the LookerUp test suite. """ -import unittest - import copy - -import random +import unittest import axelrod as axl from axelrod.load_data_ import load_pso_tables @@ -17,7 +14,7 @@ tables = load_pso_tables("pso_gambler.csv", directory="data") C, D = axl.Action.C, axl.Action.D - +random = axl.RandomGenerator() class TestGambler(TestPlayer): diff --git a/axelrod/tests/strategies/test_lookerup.py b/axelrod/tests/strategies/test_lookerup.py index c071edf3a..d88a16f15 100755 --- a/axelrod/tests/strategies/test_lookerup.py +++ b/axelrod/tests/strategies/test_lookerup.py @@ -1,10 +1,7 @@ """Test for the Looker Up strategy.""" -import unittest - import copy - -import random +import unittest import axelrod as axl from axelrod.action import str_to_actions @@ -20,7 +17,7 @@ from .test_player import TestPlayer C, D = axl.Action.C, axl.Action.D - +random = axl.RandomGenerator() class TestLookupTable(unittest.TestCase): lookup_dict = { diff --git a/axelrod/tests/strategies/test_mindreader.py b/axelrod/tests/strategies/test_mindreader.py index c582ce538..cbe903083 100644 --- a/axelrod/tests/strategies/test_mindreader.py +++ b/axelrod/tests/strategies/test_mindreader.py @@ -1,6 +1,6 @@ """Tests for the Mindreader strategy.""" -import axl as axl +import axelrod as axl from axelrod._strategy_utils import simulate_match from .test_player import TestPlayer diff --git a/axelrod/tests/strategies/test_qlearner.py b/axelrod/tests/strategies/test_qlearner.py index 81a261109..c92b0312c 100644 --- a/axelrod/tests/strategies/test_qlearner.py +++ b/axelrod/tests/strategies/test_qlearner.py @@ -1,9 +1,6 @@ """Tests for the QLearner strategies.""" -import random - import axelrod as axl - from .test_player import TestPlayer C, D = axl.Action.C, axl.Action.D diff --git a/axelrod/tests/strategies/test_retaliate.py b/axelrod/tests/strategies/test_retaliate.py index 251438735..cd8660733 100644 --- a/axelrod/tests/strategies/test_retaliate.py +++ b/axelrod/tests/strategies/test_retaliate.py @@ -2,7 +2,7 @@ import axelrod as axl -from .test_player import TestOpponent, TestPlayer +from .test_player import TestPlayer C, D = axl.Action.C, axl.Action.D diff --git a/axelrod/tests/strategies/test_selfsteem.py b/axelrod/tests/strategies/test_selfsteem.py index c0d9ec84c..d6eca8c6e 100644 --- a/axelrod/tests/strategies/test_selfsteem.py +++ b/axelrod/tests/strategies/test_selfsteem.py @@ -1,7 +1,5 @@ """Tests for the SelfSteem strategy.""" -import random - import axelrod as axl from .test_player import TestPlayer diff --git a/axelrod/tests/unit/test_deterministic_cache.py b/axelrod/tests/unit/test_deterministic_cache.py index 82d11904c..e2c1d1ae3 100644 --- a/axelrod/tests/unit/test_deterministic_cache.py +++ b/axelrod/tests/unit/test_deterministic_cache.py @@ -1,7 +1,7 @@ -import unittest import os import pathlib import pickle +import unittest import axelrod as axl from axelrod.load_data_ import axl_filename diff --git a/axelrod/tests/unit/test_eigen.py b/axelrod/tests/unit/test_eigen.py index 36f0bf5b9..707ca38c0 100644 --- a/axelrod/tests/unit/test_eigen.py +++ b/axelrod/tests/unit/test_eigen.py @@ -2,51 +2,50 @@ import unittest -import numpy +import numpy as np from numpy.testing import assert_array_almost_equal from axelrod.eigen import _normalise, principal_eigenvector - class FunctionCases(unittest.TestCase): def test_identity_matrices(self): for size in range(2, 6): - mat = numpy.identity(size) + mat = np.identity(size) evector, evalue = principal_eigenvector(mat) self.assertAlmostEqual(evalue, 1) - assert_array_almost_equal(evector, _normalise(numpy.ones(size))) + assert_array_almost_equal(evector, _normalise(np.ones(size))) def test_zero_matrix(self): - mat = numpy.array([[0, 0], [0, 0]]) + mat = np.array([[0, 0], [0, 0]]) evector, evalue = principal_eigenvector(mat) - self.assertTrue(numpy.isnan(evalue)) - self.assertTrue(numpy.isnan(evector[0])) - self.assertTrue(numpy.isnan(evector[1])) + self.assertTrue(np.isnan(evalue)) + self.assertTrue(np.isnan(evector[0])) + self.assertTrue(np.isnan(evector[1])) def test_2x2_matrix(self): - mat = numpy.array([[2, 1], [1, 2]]) + mat = np.array([[2, 1], [1, 2]]) evector, evalue = principal_eigenvector(mat) self.assertAlmostEqual(evalue, 3) - assert_array_almost_equal(evector, numpy.dot(mat, evector) / evalue) - assert_array_almost_equal(evector, _normalise(numpy.array([1, 1]))) + assert_array_almost_equal(evector, np.dot(mat, evector) / evalue) + assert_array_almost_equal(evector, _normalise(np.array([1, 1]))) def test_3x3_matrix(self): - mat = numpy.array([[1, 2, 0], [-2, 1, 2], [1, 3, 1]]) + mat = np.array([[1, 2, 0], [-2, 1, 2], [1, 3, 1]]) evector, evalue = principal_eigenvector( mat, maximum_iterations=None, max_error=1e-10 ) self.assertAlmostEqual(evalue, 3) - assert_array_almost_equal(evector, numpy.dot(mat, evector) / evalue) - assert_array_almost_equal(evector, _normalise(numpy.array([0.5, 0.5, 1]))) + assert_array_almost_equal(evector, np.dot(mat, evector) / evalue) + assert_array_almost_equal(evector, _normalise(np.array([0.5, 0.5, 1]))) def test_4x4_matrix(self): - mat = numpy.array([[2, 0, 0, 0], [1, 2, 0, 0], [0, 1, 3, 0], [0, 0, 1, 3]]) + mat = np.array([[2, 0, 0, 0], [1, 2, 0, 0], [0, 1, 3, 0], [0, 0, 1, 3]]) evector, evalue = principal_eigenvector( mat, maximum_iterations=None, max_error=1e-10 ) self.assertAlmostEqual(evalue, 3, places=3) - assert_array_almost_equal(evector, numpy.dot(mat, evector) / evalue) + assert_array_almost_equal(evector, np.dot(mat, evector) / evalue) assert_array_almost_equal( - evector, _normalise(numpy.array([0, 0, 0, 1])), decimal=4 + evector, _normalise(np.array([0, 0, 0, 1])), decimal=4 ) diff --git a/axelrod/tests/unit/test_fingerprint.py b/axelrod/tests/unit/test_fingerprint.py index 0cd437cac..c18049d28 100644 --- a/axelrod/tests/unit/test_fingerprint.py +++ b/axelrod/tests/unit/test_fingerprint.py @@ -1,19 +1,18 @@ +import matplotlib.pyplot +import os +import pathlib +from tempfile import mkstemp import unittest from unittest.mock import patch -import os -from tempfile import mkstemp -import matplotlib.pyplot +from hypothesis import given, settings import numpy as np -import pathlib import axelrod as axl from axelrod.fingerprint import AshlockFingerprint, Point, TransitiveFingerprint from axelrod.load_data_ import axl_filename from axelrod.tests.property import strategy_lists -from hypothesis import given, settings - C, D = axl.Action.C, axl.Action.D diff --git a/axelrod/tests/unit/test_graph.py b/axelrod/tests/unit/test_graph.py index 1e0666ee6..e5a66d549 100644 --- a/axelrod/tests/unit/test_graph.py +++ b/axelrod/tests/unit/test_graph.py @@ -1,6 +1,5 @@ -import unittest - from collections import defaultdict +import unittest import axelrod as axl diff --git a/axelrod/tests/unit/test_history.py b/axelrod/tests/unit/test_history.py index 29b088097..744d4f11a 100644 --- a/axelrod/tests/unit/test_history.py +++ b/axelrod/tests/unit/test_history.py @@ -1,6 +1,5 @@ -import unittest - from collections import Counter +import unittest import axelrod as axl from axelrod.history import History, LimitedHistory diff --git a/axelrod/tests/unit/test_interaction_utils.py b/axelrod/tests/unit/test_interaction_utils.py index 2e1d2c5e1..6dfb3c3e2 100644 --- a/axelrod/tests/unit/test_interaction_utils.py +++ b/axelrod/tests/unit/test_interaction_utils.py @@ -1,6 +1,6 @@ -import unittest -import tempfile from collections import Counter +import tempfile +import unittest import axelrod as axl diff --git a/axelrod/tests/unit/test_match.py b/axelrod/tests/unit/test_match.py index 82df07bd4..d652c06c9 100644 --- a/axelrod/tests/unit/test_match.py +++ b/axelrod/tests/unit/test_match.py @@ -1,12 +1,10 @@ -import unittest - from collections import Counter +import unittest -import axelrod as axl from hypothesis import example, given from hypothesis.strategies import floats, integers -from axelrod import Action +import axelrod as axl from axelrod.deterministic_cache import DeterministicCache from axelrod.random_ import RandomGenerator from axelrod.tests.property import games diff --git a/axelrod/tests/unit/test_moran.py b/axelrod/tests/unit/test_moran.py index 5a48f1171..4d4a4a26d 100644 --- a/axelrod/tests/unit/test_moran.py +++ b/axelrod/tests/unit/test_moran.py @@ -1,8 +1,9 @@ -import unittest -import itertools -import random from collections import Counter +import itertools +import unittest + import matplotlib.pyplot as plt + import axelrod as axl from axelrod import MoranProcess from axelrod.tests.property import strategy_lists @@ -12,6 +13,7 @@ C, D = axl.Action.C, axl.Action.D +random = axl.RandomGenerator() class TestMoranProcess(unittest.TestCase): diff --git a/axelrod/tests/unit/test_pickling.py b/axelrod/tests/unit/test_pickling.py index da2a9fc1f..6ef0c220c 100644 --- a/axelrod/tests/unit/test_pickling.py +++ b/axelrod/tests/unit/test_pickling.py @@ -1,11 +1,10 @@ -import unittest import pickle -import random +import unittest import axelrod as axl C, D = axl.Action.C, axl.Action.D - +random = axl.RandomGenerator() # A set of classes to test pickling. diff --git a/axelrod/tests/unit/test_plot.py b/axelrod/tests/unit/test_plot.py index 89d40d8d9..5c6093e7d 100644 --- a/axelrod/tests/unit/test_plot.py +++ b/axelrod/tests/unit/test_plot.py @@ -1,10 +1,9 @@ +import pathlib +import tempfile import unittest -import tempfile import matplotlib import matplotlib.pyplot as plt -import pathlib - from numpy import mean import axelrod as axl diff --git a/axelrod/tests/unit/test_random_.py b/axelrod/tests/unit/test_random_.py index 68b72728f..b8e98db78 100644 --- a/axelrod/tests/unit/test_random_.py +++ b/axelrod/tests/unit/test_random_.py @@ -1,14 +1,10 @@ """Tests for the random functions.""" -import unittest - -import random - from collections import Counter - -import numpy -from axelrod import Action, BulkRandomGenerator, Pdf, RandomGenerator +import unittest import axelrod as axl +from axelrod import BulkRandomGenerator, Pdf, RandomGenerator + C, D = axl.Action.C, axl.Action.D diff --git a/axelrod/tests/unit/test_resultset.py b/axelrod/tests/unit/test_resultset.py index 27b71a79f..e9ae42617 100644 --- a/axelrod/tests/unit/test_resultset.py +++ b/axelrod/tests/unit/test_resultset.py @@ -1,11 +1,10 @@ -import unittest -import csv from collections import Counter +import csv +import pathlib +import unittest -import pandas as pd -from dask.dataframe.core import DataFrame from numpy import mean, nanmedian, std -import pathlib +import pandas as pd import axelrod as axl from axelrod.load_data_ import axl_filename diff --git a/axelrod/tests/unit/test_tournament.py b/axelrod/tests/unit/test_tournament.py index 907bd451d..dfee77654 100644 --- a/axelrod/tests/unit/test_tournament.py +++ b/axelrod/tests/unit/test_tournament.py @@ -1,14 +1,15 @@ """Tests for the main tournament class.""" -import unittest -from unittest.mock import MagicMock, patch import io import logging +from multiprocessing import Queue, cpu_count import os import pathlib import pickle +import unittest +from unittest.mock import MagicMock, patch import warnings -from multiprocessing import Queue, cpu_count + from hypothesis import example, given, settings from hypothesis.strategies import floats, integers diff --git a/axelrod/tournament.py b/axelrod/tournament.py index 7e8f666ce..2f301e31e 100644 --- a/axelrod/tournament.py +++ b/axelrod/tournament.py @@ -7,8 +7,9 @@ from tempfile import mkstemp from typing import List, Optional, Tuple -import axelrod.interaction_utils as iu import tqdm + +import axelrod.interaction_utils as iu from axelrod import DEFAULT_TURNS from axelrod.action import Action, actions_to_str from axelrod.player import Player From af3c387c63d59d5f15d2416050fbe2dff4424fff Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Mon, 20 Apr 2020 20:53:25 -0700 Subject: [PATCH 008/121] Update many tests with new random seeds and values, up to axelrod_first.py --- axelrod/player.py | 5 +- axelrod/strategies/axelrod_first.py | 7 -- axelrod/tests/integration/test_filtering.py | 1 + axelrod/tests/integration/test_matches.py | 2 +- axelrod/tests/integration/test_tournament.py | 2 +- axelrod/tests/strategies/test_adaptor.py | 6 +- .../tests/strategies/test_averagecopier.py | 36 +++--- .../tests/strategies/test_axelrod_first.py | 62 ++++------ axelrod/tests/strategies/test_player.py | 6 +- axelrod/tests/unit/test_fingerprint.py | 110 +++++++++--------- axelrod/tests/unit/test_match_generator.py | 8 +- axelrod/tests/unit/test_pickling.py | 2 +- axelrod/tests/unit/test_random_.py | 14 ++- 13 files changed, 122 insertions(+), 139 deletions(-) diff --git a/axelrod/player.py b/axelrod/player.py index 254f5d6c2..950a86b5e 100644 --- a/axelrod/player.py +++ b/axelrod/player.py @@ -3,6 +3,7 @@ import itertools import types from typing import Any, Dict +import warnings import numpy as np @@ -121,7 +122,9 @@ def set_match_attributes(self, length=-1, game=None, noise=0): def set_seed(self, seed=None): """Set a random seed for the player's random number generator.""" if seed is None: - # Warning: using global seed + warnings.warn( + "Initializing player with seed from Axelrod module random number generator." + " Results may not be seed reproducible.") self._seed = _module_random.random_seed_int() else: self._seed = seed diff --git a/axelrod/strategies/axelrod_first.py b/axelrod/strategies/axelrod_first.py index a606ed943..9c2614938 100644 --- a/axelrod/strategies/axelrod_first.py +++ b/axelrod/strategies/axelrod_first.py @@ -745,15 +745,8 @@ class FirstByTullock(Player): } def __init__(self) -> None: - """ - Parameters - ---------- - rounds_to_cooperate: int - The number of rounds to cooperate initially - """ super().__init__() self._rounds_to_cooperate = 11 - self.memory_depth = self._rounds_to_cooperate def strategy(self, opponent: Player) -> Action: if len(self.history) < self._rounds_to_cooperate: diff --git a/axelrod/tests/integration/test_filtering.py b/axelrod/tests/integration/test_filtering.py index d53792d0f..a564e3e58 100644 --- a/axelrod/tests/integration/test_filtering.py +++ b/axelrod/tests/integration/test_filtering.py @@ -21,6 +21,7 @@ def setUp(self) -> None: def tearDown(self) -> None: warnings.simplefilter("default", category=UserWarning) + @settings(deadline=None) @given(strategies=strategy_lists(min_size=20, max_size=20)) def test_boolean_filtering(self, strategies): diff --git a/axelrod/tests/integration/test_matches.py b/axelrod/tests/integration/test_matches.py index 50f56dbf2..a83f1f129 100644 --- a/axelrod/tests/integration/test_matches.py +++ b/axelrod/tests/integration/test_matches.py @@ -47,7 +47,7 @@ def test_outcome_repeats_stochastic(self, strategies, turns, seed): results = [] for _ in range(3): players = [s() for s in strategies] - results.append(axelrod.Match(players, turns=turns, seed=seed).play()) + results.append(axl.Match(players, turns=turns, seed=seed).play()) self.assertEqual(results[0], results[1]) self.assertEqual(results[1], results[2]) diff --git a/axelrod/tests/integration/test_tournament.py b/axelrod/tests/integration/test_tournament.py index 30886632a..6300f8fc1 100644 --- a/axelrod/tests/integration/test_tournament.py +++ b/axelrod/tests/integration/test_tournament.py @@ -164,7 +164,7 @@ def test_matches_have_different_length(self): p2 = axl.Cooperator() p3 = axl.Cooperator() players = [p1, p2, p3] - tournament = axelrod.Tournament(players, prob_end=0.5, repetitions=2, seed=3) + tournament = axl.Tournament(players, prob_end=0.5, repetitions=2, seed=3) results = tournament.play(progress_bar=False) # Check that match length are different across the repetitions self.assertNotEqual(results.match_lengths[0], results.match_lengths[1]) diff --git a/axelrod/tests/strategies/test_adaptor.py b/axelrod/tests/strategies/test_adaptor.py index 5ec28f24a..dad77971d 100644 --- a/axelrod/tests/strategies/test_adaptor.py +++ b/axelrod/tests/strategies/test_adaptor.py @@ -29,13 +29,13 @@ def test_strategy(self): # Error corrected. actions = [(C, C), (C, D), (D, C), (C, C)] self.versus_test( - opponent=axl.AdaptorBrief(), expected_actions=actions, seed=22 + opponent=axl.AdaptorBrief(), expected_actions=actions, seed=245 ) # Error corrected, example 2 actions = [(D, C), (C, D), (D, C), (C, D), (C, C)] self.versus_test( - opponent=axl.AdaptorBrief(), expected_actions=actions, seed=925 + opponent=axl.AdaptorBrief(), expected_actions=actions, seed=7935 ) # Versus Cooperator @@ -74,7 +74,7 @@ def test_strategy(self): # Error corrected. actions = [(C, C), (C, D), (D, D), (C, C), (C, C)] self.versus_test( - opponent=axl.AdaptorLong(), expected_actions=actions, seed=22 + opponent=axl.AdaptorLong(), expected_actions=actions, seed=305 ) # Versus Cooperator diff --git a/axelrod/tests/strategies/test_averagecopier.py b/axelrod/tests/strategies/test_averagecopier.py index ae23667e7..965990a30 100644 --- a/axelrod/tests/strategies/test_averagecopier.py +++ b/axelrod/tests/strategies/test_averagecopier.py @@ -45,7 +45,7 @@ def test_strategy(self): (C, D), (C, C), (D, D), - (D, C), + (C, C), (C, D), ] self.versus_test(axl.Alternator(), expected_actions=actions, seed=1) @@ -53,11 +53,11 @@ def test_strategy(self): actions = [ (D, C), (C, D), - (D, C), + (C, C), (C, D), (C, C), (D, D), - (D, C), + (C, C), (D, D), (C, C), (D, D), @@ -73,9 +73,9 @@ def test_strategy(self): (D, D), (C, D), (D, C), - (D, C), - (D, D), + (C, C), (D, D), + (C, D), ] self.versus_test(opponent, expected_actions=actions, seed=1) @@ -85,12 +85,12 @@ def test_strategy(self): (C, C), (C, C), (C, D), - (D, D), + (C, D), (C, D), (C, C), (D, C), - (D, C), - (D, D), + (C, C), + (C, D), ] self.versus_test(opponent, expected_actions=actions, seed=2) @@ -126,10 +126,10 @@ def test_strategy(self): (D, D), (D, C), (C, D), - (C, C), + (D, C), (C, D), (D, C), - (D, D), + (C, D), ] self.versus_test(axl.Alternator(), expected_actions=actions, seed=1) @@ -137,11 +137,11 @@ def test_strategy(self): (C, C), (C, D), (D, C), - (D, D), - (C, C), (C, D), (D, C), - (D, D), + (C, D), + (D, C), + (C, D), (D, C), (C, D), ] @@ -155,7 +155,7 @@ def test_strategy(self): (C, D), (D, D), (D, D), - (C, C), + (D, C), (D, C), (C, D), (D, D), @@ -168,11 +168,11 @@ def test_strategy(self): (C, C), (C, C), (C, D), - (D, D), - (D, D), - (C, C), + (C, D), + (C, D), + (D, C), (C, C), (D, C), - (D, D), + (C, D), ] self.versus_test(opponent, expected_actions=actions, seed=2) diff --git a/axelrod/tests/strategies/test_axelrod_first.py b/axelrod/tests/strategies/test_axelrod_first.py index 35ee2d5f4..d36e747c3 100644 --- a/axelrod/tests/strategies/test_axelrod_first.py +++ b/axelrod/tests/strategies/test_axelrod_first.py @@ -127,10 +127,10 @@ def test_decay(self): self.assertEqual(player._cooperation_probability(), player._end_coop_prob) def test_strategy(self): - actions = [(C, C)] * 41 + [(D, C)] + actions = [(C, C)] * 13 + [(D, C)] self.versus_test(axl.Cooperator(), expected_actions=actions, seed=1) - actions = [(C, C)] * 16 + [(D, C)] + actions = [(C, C)] * 11 + [(D, C)] self.versus_test(axl.Cooperator(), expected_actions=actions, seed=2) actions = [(C, D)] + [(D, D)] * 20 @@ -163,19 +163,19 @@ def test_strategy(self): actions += [(D, C)] # 51 turns actions += [(C, D), (D, C)] * 2 + [(C, D)] # 56 turns self.versus_test( - axl.Alternator(), expected_actions=actions, attrs=expected_attrs + axl.Alternator(), expected_actions=actions, attrs=expected_attrs, seed=0 ) # Against defector actions = [(C, D)] + [(D, D)] * 55 # 56 turns self.versus_test( - axl.Defector(), expected_actions=actions, attrs=expected_attrs + axl.Defector(), expected_actions=actions, attrs=expected_attrs, seed=0 ) # Against cooperator actions = [(C, C)] * 50 + [(D, C)] + [(C, C)] * 5 self.versus_test( - axl.Cooperator(), expected_actions=actions, attrs=expected_attrs + axl.Cooperator(), expected_actions=actions, attrs=expected_attrs, seed=0 ) # Test recognition of random player @@ -185,12 +185,12 @@ def test_strategy(self): } actions = [(C, C)] * 50 + [(D, C)] + [(C, C)] * 5 # 56 turns self.versus_test( - axl.Cooperator(), expected_actions=actions, attrs=expected_attrs + axl.Cooperator(), expected_actions=actions, attrs=expected_attrs, seed=1 ) - expected_attrs = {"opponent_is_random": False, "next_random_defection_turn": 68} + expected_attrs = {"opponent_is_random": False, "next_random_defection_turn": 63} actions += [(C, C)] # 57 turns self.versus_test( - axl.Cooperator(), expected_actions=actions, attrs=expected_attrs + axl.Cooperator(), expected_actions=actions, attrs=expected_attrs, seed=8 ) expected_attrs = { @@ -202,11 +202,11 @@ def test_strategy(self): actions += [(C, D), (D, C)] * 3 # 57 turns actions += [(D, D)] self.versus_test( - axl.Alternator(), expected_actions=actions, attrs=expected_attrs + axl.Alternator(), expected_actions=actions, attrs=expected_attrs, seed=3 ) actions += [(D, C), (D, D)] * 5 self.versus_test( - axl.Alternator(), expected_actions=actions, attrs=expected_attrs + axl.Alternator(), expected_actions=actions, attrs=expected_attrs, seed=4 ) # Test versus TfT @@ -222,16 +222,16 @@ def test_strategy(self): ) # Test random defections - expected_attrs = {"opponent_is_random": False, "next_random_defection_turn": 78} - actions = [(C, C)] * 50 + [(D, C)] + [(C, C)] * 16 + [(D, C)] + [(C, C)] + expected_attrs = {"opponent_is_random": False, "next_random_defection_turn": 76} + actions = [(C, C)] * 50 + [(D, C)] + [(C, C)] * 15 + [(D, C)] + [(C, C)] self.versus_test( axl.Cooperator(), expected_actions=actions, seed=0, attrs=expected_attrs ) - expected_attrs = {"opponent_is_random": False, "next_random_defection_turn": 77} - actions = [(C, C)] * 50 + [(D, C)] + [(C, C)] * 12 + [(D, C)] + [(C, C)] + expected_attrs = {"opponent_is_random": False, "next_random_defection_turn": 79} + actions = [(C, C)] * 50 + [(D, C)] + [(C, C)] * 14 + [(D, C)] + [(C, C)] self.versus_test( - axl.Cooperator(), expected_actions=actions, seed=1, attrs=expected_attrs + axl.Cooperator(), expected_actions=actions, seed=5, attrs=expected_attrs ) @@ -251,17 +251,17 @@ class TestFirstByGrofman(TestPlayer): def test_strategy(self): actions = [(C, C)] * 7 - self.versus_test(axl.Cooperator(), expected_actions=actions) + self.versus_test(axl.Cooperator(), expected_actions=actions, seed=0) actions = [(C, C), (C, D), (D, C)] - self.versus_test(axl.Alternator(), expected_actions=actions) + self.versus_test(axl.Alternator(), expected_actions=actions, seed=0) opponent = axl.MockPlayer(actions=[D] * 8) - actions = [(C, D), (C, D), (D, D), (C, D), (D, D), (C, D), (C, D), (D, D)] + actions = [(C, D), (C, D), (D, D), (C, D), (D, D), (C, D), (D, D), (C, D)] self.versus_test(opponent, expected_actions=actions, seed=1) opponent = axl.MockPlayer(actions=[D] * 8) - actions = [(C, D), (D, D), (C, D), (D, D), (C, D), (C, D), (C, D), (D, D)] + actions = [(C, D), (D, D), (C, D), (D, D), (C, D), (D, D), (C, D), (C, D)] self.versus_test(opponent, expected_actions=actions, seed=2) @@ -284,10 +284,10 @@ def test_four_vector(self): test_four_vector(self, expected_dictionary) def test_strategy(self): - actions = [(C, C), (C, C), (C, C), (C, C)] + actions = [(C, C), (C, C), (C, C), (D, C)] self.versus_test(axl.Cooperator(), expected_actions=actions, seed=1) - actions = [(C, C), (D, C), (D, C), (C, C)] + actions = [(C, C), (C, C), (C, C), (C, C)] self.versus_test(axl.Cooperator(), expected_actions=actions, seed=2) actions = [(C, D), (D, D), (D, D), (D, D)] @@ -424,22 +424,10 @@ def test_strategy(self): self.versus_test(opponent, expected_actions=actions) # Test beyond 10 rounds - opponent = axl.MockPlayer(actions=[D] * 5 + [C] * 6) - actions = [(C, D)] * 5 + [(C, C)] * 6 + [(D, D)] * 4 + opponent = axl.MockPlayer(actions=[D] * 5 + [C] * 5 + [C, D] * 5) + actions = [(C, D)] * 5 + [(C, C)] * 6 + [(D, D)] + [(D, C), (C, D), (C, C)] self.versus_test(opponent, expected_actions=actions, seed=20) - opponent = axl.MockPlayer(actions=[D] * 5 + [C] * 6) - actions = [(C, D)] * 5 + [(C, C)] * 6 + [(C, D), (D, D), (D, D), (C, D)] - self.versus_test(opponent, expected_actions=actions, seed=1) - - opponent = axl.MockPlayer(actions=[C] * 9 + [D] * 2) - actions = [(C, C)] * 9 + [(C, D)] * 2 + [(C, C), (D, C), (D, C), (C, C)] - self.versus_test(opponent, expected_actions=actions, seed=1) - - opponent = axl.MockPlayer(actions=[C] * 9 + [D] * 2) - actions = [(C, C)] * 9 + [(C, D)] * 2 + [(D, C), (D, C), (C, C), (C, C)] - self.versus_test(opponent, expected_actions=actions, seed=2) - class TestFirstByAnonymous(TestPlayer): @@ -456,10 +444,10 @@ class TestFirstByAnonymous(TestPlayer): } def test_strategy(self): - actions = [(D, C), (C, C), (C, C), (D, C), (C, C), (C, C)] + actions = [(D, C), (C, C), (C, C), (C, C), (D, C), (C, C)] self.versus_test(axl.Cooperator(), expected_actions=actions, seed=1) - actions = [(C, C), (C, C), (D, C), (C, C), (C, C), (D, C)] + actions = [(D, C), (D, C), (D, C), (C, C), (D, C), (D, C)] self.versus_test(axl.Cooperator(), expected_actions=actions, seed=10) diff --git a/axelrod/tests/strategies/test_player.py b/axelrod/tests/strategies/test_player.py index 0bbbb75ee..e9f7488b6 100644 --- a/axelrod/tests/strategies/test_player.py +++ b/axelrod/tests/strategies/test_player.py @@ -10,11 +10,8 @@ import axelrod as axl from axelrod.tests.property import strategy_lists - C, D = axl.Action.C, axl.Action.D - random = axl.RandomGenerator() - short_run_time_short_mem = [ s for s in axl.short_run_time_strategies @@ -487,7 +484,7 @@ def test_clone(self, seed): axl.Cooperator(), axl.Defector(), axl.TitForTat(), - axl.Random(p=r), + axl.Random(p=0.5), ]: for p in [player1, player2]: m = axl.Match((p, op), turns=turns, reset=True, seed=seed) @@ -687,7 +684,6 @@ def test_memory(player, opponent, memory_length, seed=0, turns=10): opponent.reset() player._history = axl.LimitedHistory(memory_length) opponent._history = axl.LimitedHistory(memory_length) - axl.seed(seed) match = axl.Match((player, opponent), turns=turns, reset=False, seed=seed) limited_plays = [p[0] for p in match.play()] diff --git a/axelrod/tests/unit/test_fingerprint.py b/axelrod/tests/unit/test_fingerprint.py index c18049d28..47cd305d9 100644 --- a/axelrod/tests/unit/test_fingerprint.py +++ b/axelrod/tests/unit/test_fingerprint.py @@ -248,7 +248,7 @@ def test_plot_data(self): af = AshlockFingerprint(axl.Cooperator()) af.fingerprint(turns=5, repetitions=3, step=0.5, progress_bar=False, seed=0) - reshaped_data = np.array([[0.0, 0.0, 0.0], [2.0, 1.0, 2.0], [3.0, 3.0, 3.0]]) + reshaped_data = np.array([[0.0, 0.0, 0.0], [1.4, 1.8, 1.2], [3.0, 3.0, 3.0]]) plotted_data = af.plot().gca().images[0].get_array() np.testing.assert_allclose(plotted_data, reshaped_data) @@ -271,29 +271,29 @@ def test_plot_figure(self): def test_wsls_fingerprint(self): test_data = { Point(x=0.0, y=0.0): 3.000, - Point(x=0.0, y=0.25): 1.710, - Point(x=0.0, y=0.5): 1.440, - Point(x=0.0, y=0.75): 1.080, + Point(x=0.0, y=0.25): 1.740, + Point(x=0.0, y=0.5): 1.300, + Point(x=0.0, y=0.75): 0.900, Point(x=0.0, y=1.0): 0.500, Point(x=0.25, y=0.0): 3.000, - Point(x=0.25, y=0.25): 2.280, - Point(x=0.25, y=0.5): 1.670, - Point(x=0.25, y=0.75): 1.490, - Point(x=0.25, y=1.0): 0.770, + Point(x=0.25, y=0.25): 2.250, + Point(x=0.25, y=0.5): 1.990, + Point(x=0.25, y=0.75): 1.250, + Point(x=0.25, y=1.0): 0.670, Point(x=0.5, y=0.0): 3.000, - Point(x=0.5, y=0.25): 2.740, - Point(x=0.5, y=0.5): 2.240, - Point(x=0.5, y=0.75): 1.730, - Point(x=0.5, y=1.0): 1.000, + Point(x=0.5, y=0.25): 2.700, + Point(x=0.5, y=0.5): 2.420, + Point(x=0.5, y=0.75): 1.700, + Point(x=0.5, y=1.0): 0.950, Point(x=0.75, y=0.0): 3.000, - Point(x=0.75, y=0.25): 3.520, - Point(x=0.75, y=0.5): 2.830, - Point(x=0.75, y=0.75): 1.750, - Point(x=0.75, y=1.0): 1.250, + Point(x=0.75, y=0.25): 2.920, + Point(x=0.75, y=0.5): 3.000, + Point(x=0.75, y=0.75): 2.770, + Point(x=0.75, y=1.0): 1.100, Point(x=1.0, y=0.0): 3.000, - Point(x=1.0, y=0.25): 4.440, - Point(x=1.0, y=0.5): 4.410, - Point(x=1.0, y=0.75): 4.440, + Point(x=1.0, y=0.25): 4.800, + Point(x=1.0, y=0.5): 3.860, + Point(x=1.0, y=0.75): 4.380, Point(x=1.0, y=1.0): 1.300, } af = axl.AshlockFingerprint(axl.WinStayLoseShift(), axl.TitForTat) @@ -306,29 +306,29 @@ def test_wsls_fingerprint(self): def test_tft_fingerprint(self): test_data = { Point(x=0.0, y=0.0): 3.000, - Point(x=0.0, y=0.25): 1.820, - Point(x=0.0, y=0.5): 1.130, - Point(x=0.0, y=0.75): 1.050, + Point(x=0.0, y=0.25): 1.100, + Point(x=0.0, y=0.5): 1.070, + Point(x=0.0, y=0.75): 0.980, Point(x=0.0, y=1.0): 0.980, Point(x=0.25, y=0.0): 3.000, - Point(x=0.25, y=0.25): 2.440, - Point(x=0.25, y=0.5): 1.770, - Point(x=0.25, y=0.75): 1.700, - Point(x=0.25, y=1.0): 1.490, + Point(x=0.25, y=0.25): 2.250, + Point(x=0.25, y=0.5): 1.880, + Point(x=0.25, y=0.75): 1.640, + Point(x=0.25, y=1.0): 1.460, Point(x=0.5, y=0.0): 3.000, - Point(x=0.5, y=0.25): 2.580, - Point(x=0.5, y=0.5): 2.220, - Point(x=0.5, y=0.75): 2.000, - Point(x=0.5, y=1.0): 1.940, + Point(x=0.5, y=0.25): 2.540, + Point(x=0.5, y=0.5): 2.330, + Point(x=0.5, y=0.75): 2.100, + Point(x=0.5, y=1.0): 1.830, Point(x=0.75, y=0.0): 3.000, - Point(x=0.75, y=0.25): 2.730, - Point(x=0.75, y=0.5): 2.290, - Point(x=0.75, y=0.75): 2.310, - Point(x=0.75, y=1.0): 2.130, + Point(x=0.75, y=0.25): 2.780, + Point(x=0.75, y=0.5): 2.520, + Point(x=0.75, y=0.75): 2.250, + Point(x=0.75, y=1.0): 1.980, Point(x=1.0, y=0.0): 3.000, - Point(x=1.0, y=0.25): 2.790, - Point(x=1.0, y=0.5): 2.480, - Point(x=1.0, y=0.75): 2.310, + Point(x=1.0, y=0.25): 2.770, + Point(x=1.0, y=0.5): 2.460, + Point(x=1.0, y=0.75): 2.360, Point(x=1.0, y=1.0): 2.180, } @@ -342,29 +342,29 @@ def test_tft_fingerprint(self): def test_majority_fingerprint(self): test_data = { Point(x=0.0, y=0.0): 3.000, - Point(x=0.0, y=0.25): 1.940, - Point(x=0.0, y=0.5): 1.130, - Point(x=0.0, y=0.75): 1.030, + Point(x=0.0, y=0.25): 1.630, + Point(x=0.0, y=0.5): 1.070, + Point(x=0.0, y=0.75): 0.980, Point(x=0.0, y=1.0): 0.980, Point(x=0.25, y=0.0): 3.000, - Point(x=0.25, y=0.25): 2.130, - Point(x=0.25, y=0.5): 1.940, - Point(x=0.25, y=0.75): 2.060, - Point(x=0.25, y=1.0): 1.940, + Point(x=0.25, y=0.25): 2.100, + Point(x=0.25, y=0.5): 1.840, + Point(x=0.25, y=0.75): 1.880, + Point(x=0.25, y=1.0): 1.700, Point(x=0.5, y=0.0): 3.000, - Point(x=0.5, y=0.25): 2.300, - Point(x=0.5, y=0.5): 2.250, - Point(x=0.5, y=0.75): 2.420, - Point(x=0.5, y=1.0): 2.690, + Point(x=0.5, y=0.25): 2.310, + Point(x=0.5, y=0.5): 2.270, + Point(x=0.5, y=0.75): 2.670, + Point(x=0.5, y=1.0): 2.580, Point(x=0.75, y=0.0): 3.000, - Point(x=0.75, y=0.25): 2.400, - Point(x=0.75, y=0.5): 2.010, - Point(x=0.75, y=0.75): 2.390, - Point(x=0.75, y=1.0): 2.520, + Point(x=0.75, y=0.25): 2.440, + Point(x=0.75, y=0.5): 1.880, + Point(x=0.75, y=0.75): 2.030, + Point(x=0.75, y=1.0): 2.540, Point(x=1.0, y=0.0): 3.000, - Point(x=1.0, y=0.25): 2.360, - Point(x=1.0, y=0.5): 1.740, - Point(x=1.0, y=0.75): 2.260, + Point(x=1.0, y=0.25): 2.370, + Point(x=1.0, y=0.5): 1.920, + Point(x=1.0, y=0.75): 2.070, Point(x=1.0, y=1.0): 2.260, } diff --git a/axelrod/tests/unit/test_match_generator.py b/axelrod/tests/unit/test_match_generator.py index ef8de70c8..35024a2af 100644 --- a/axelrod/tests/unit/test_match_generator.py +++ b/axelrod/tests/unit/test_match_generator.py @@ -184,7 +184,7 @@ def test_build_match_chunks(self, repetitions): seed=integers(min_value=1, max_value=4294967295),) @settings(max_examples=5) def test_seeding_equality(self, repetitions, seed): - rr1 = axelrod.MatchGenerator( + rr1 = axl.MatchGenerator( players=self.players, turns=test_turns, game=test_game, @@ -192,7 +192,7 @@ def test_seeding_equality(self, repetitions, seed): seed=seed ) chunks1 = list(rr1.build_match_chunks()) - rr2 = axelrod.MatchGenerator( + rr2 = axl.MatchGenerator( players=self.players, turns=test_turns, game=test_game, @@ -203,7 +203,7 @@ def test_seeding_equality(self, repetitions, seed): self.assertEqual(chunks1, chunks2) def test_seeding_inequality(self, repetitions=10): - rr1 = axelrod.MatchGenerator( + rr1 = axl.MatchGenerator( players=self.players, turns=test_turns, game=test_game, @@ -211,7 +211,7 @@ def test_seeding_inequality(self, repetitions=10): seed=0 ) chunks1 = list(rr1.build_match_chunks()) - rr2 = axelrod.MatchGenerator( + rr2 = axl.MatchGenerator( players=self.players, turns=test_turns, game=test_game, diff --git a/axelrod/tests/unit/test_pickling.py b/axelrod/tests/unit/test_pickling.py index 6ef0c220c..29f25d645 100644 --- a/axelrod/tests/unit/test_pickling.py +++ b/axelrod/tests/unit/test_pickling.py @@ -254,7 +254,7 @@ def test_final_transformer_called(self): self.assertEqual(results, [(C, C), (C, C), (D, D)]) def test_pickling_all_strategies(self): - for s in random.sample(axl.strategies, 50): + for s in random.choice(axl.strategies, 50): with self.subTest(strategy=s.name): self.assert_original_equals_pickled(s()) diff --git a/axelrod/tests/unit/test_random_.py b/axelrod/tests/unit/test_random_.py index b8e98db78..b7da3d7d8 100644 --- a/axelrod/tests/unit/test_random_.py +++ b/axelrod/tests/unit/test_random_.py @@ -11,13 +11,15 @@ class TestRandomGenerator(unittest.TestCase): def test_return_values(self): - random = RandomGenerator() - self.assertEqual(random.random_choice(1), C) - self.assertEqual(random.random_choice(0), D) + # The seed doesn't matter for p=0 or p=1 + for seed in range(10): + random = RandomGenerator(seed=seed) + self.assertEqual(random.random_choice(1), C) + self.assertEqual(random.random_choice(0), D) random.seed(1) - self.assertEqual(random.random_choice(), C) - random.seed(2) - self.assertEqual(random.random_choice(), D) + self.assertEqual(random.random_choice(0.5), C) + random.seed(3) + self.assertEqual(random.random_choice(0.5), D) def test_seed_not_offset_by_deterministic_call(self): """Test that when called with p = 0 or 1, the random seed is not From b2dc6cd262f4b5ddf2ddbcaee360b8f0f0d53a3a Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Mon, 20 Apr 2020 23:19:36 -0700 Subject: [PATCH 009/121] Fix many more tests --- axelrod/evolvable_player.py | 1 + axelrod/strategies/cycler.py | 6 +- axelrod/strategies/darwin.py | 1 - .../strategies/test_better_and_better.py | 73 ++++--------------- axelrod/tests/strategies/test_cycler.py | 12 +-- axelrod/tests/strategies/test_geller.py | 2 +- axelrod/tests/strategies/test_headsup.py | 8 +- axelrod/tests/strategies/test_hmm.py | 2 +- axelrod/tests/strategies/test_inverse.py | 27 +++---- axelrod/tests/strategies/test_memorytwo.py | 2 +- axelrod/tests/strategies/test_player.py | 61 ++++++++-------- axelrod/tests/strategies/test_rand.py | 2 +- axelrod/tests/strategies/test_titfortat.py | 17 +++-- 13 files changed, 80 insertions(+), 134 deletions(-) diff --git a/axelrod/evolvable_player.py b/axelrod/evolvable_player.py index 864f8746c..d73e45a31 100644 --- a/axelrod/evolvable_player.py +++ b/axelrod/evolvable_player.py @@ -22,6 +22,7 @@ class EvolvablePlayer(Player): def __init__(self, seed=None): super().__init__() + # if set_seed: self.set_seed(seed=seed) def overwrite_init_kwargs(self, **kwargs): diff --git a/axelrod/strategies/cycler.py b/axelrod/strategies/cycler.py index 645108c7a..8c678af07 100644 --- a/axelrod/strategies/cycler.py +++ b/axelrod/strategies/cycler.py @@ -111,11 +111,9 @@ def __init__( mutation_potency: int = 1, seed: int = None ) -> None: - self.set_seed(seed=seed) + EvolvablePlayer.__init__(self, seed=seed) cycle, cycle_length = self._normalize_parameters(cycle, cycle_length) - # The following __init__ sets self.cycle = cycle Cycler.__init__(self, cycle=cycle) - EvolvablePlayer.__init__(self) # Overwrite init_kwargs in the case that we generated a new cycle from cycle_length self.overwrite_init_kwargs( cycle=cycle, @@ -162,7 +160,7 @@ def crossover(self, other) -> EvolvablePlayer: cycle_list = crossover_lists(self.cycle, other.cycle, self._random) cycle = "".join(cycle_list) cycle, _ = self._normalize_parameters(cycle) - return self.create_new(cycle=cycle) + return self.create_new(cycle=cycle, seed=self._random.random_seed_int()) class CyclerDC(Cycler): diff --git a/axelrod/strategies/darwin.py b/axelrod/strategies/darwin.py index 93db009eb..974f25ca4 100644 --- a/axelrod/strategies/darwin.py +++ b/axelrod/strategies/darwin.py @@ -3,7 +3,6 @@ indicated by their classifier). We do not recommend putting a lot of time in to optimising it. """ -from collections import defaultdict from typing import Optional from axelrod.action import Action diff --git a/axelrod/tests/strategies/test_better_and_better.py b/axelrod/tests/strategies/test_better_and_better.py index f07060975..2499eb4a3 100644 --- a/axelrod/tests/strategies/test_better_and_better.py +++ b/axelrod/tests/strategies/test_better_and_better.py @@ -23,72 +23,29 @@ class TestBetterAndBetter(TestPlayer): def test_strategy(self): """Tests that the strategy gives expected behaviour.""" + expected_actions = [(D, D)] * 90 + [(C, D)] self.versus_test( axl.Defector(), - expected_actions=[ - (D, D), - (D, D), - (D, D), - (D, D), - (C, D), - (D, D), - (D, D), - (D, D), - (D, D), - ], + expected_actions=expected_actions, seed=6, ) + expected_actions = [(D, C)] * 10 self.versus_test( axl.Cooperator(), - expected_actions=[ - (D, C), - (D, C), - (D, C), - (D, C), - (D, C), - (D, C), - (D, C), - (D, C), - (D, C), - ], + expected_actions=expected_actions, seed=8, ) + expected_actions = [(D, D)] * 41 + [(C, D)] self.versus_test( axl.Defector(), - expected_actions=[ - (C, D), - (D, D), - (D, D), - (D, D), - (D, D), - (D, D), - (D, D), - (D, D), - (D, D), - ], - seed=1514, + expected_actions=expected_actions, + seed=13, ) - actions = [] - for index in range(200): - if index in [ - 64, - 79, - 91, - 99, - 100, - 107, - 111, - 119, - 124, - 127, - 137, - 141, - 144, - 154, - 192, - 196, - ]: - actions.append((C, D)) - else: - actions.append((D, D)) - self.versus_test(axl.Defector(), expected_actions=actions, seed=8) + expected_indices = [18, 39, 49, 67, 77, 116, 139, 142, 149] + m = axl.Match((self.player(), axl.Defector()), turns=150, seed=111) + result = m.play() + indices = [] + for index, actions in enumerate(result): + if actions == (C, D): + indices.append(index) + self.assertEqual(expected_indices, indices) diff --git a/axelrod/tests/strategies/test_cycler.py b/axelrod/tests/strategies/test_cycler.py index 126cd3709..08d94200c 100644 --- a/axelrod/tests/strategies/test_cycler.py +++ b/axelrod/tests/strategies/test_cycler.py @@ -180,20 +180,20 @@ def test_normalized_parameters(self): def test_crossover_even_length(self): cycle1 = "C" * 6 cycle2 = "D" * 6 - cross_cycle = "CCCDDD" + cross_cycle = "CCCCCD" - player1 = self.player_class(cycle=cycle1, seed=3) - player2 = self.player_class(cycle=cycle2) + player1 = self.player_class(cycle=cycle1, seed=4) + player2 = self.player_class(cycle=cycle2, seed=5) crossed = player1.crossover(player2) self.assertEqual(cross_cycle, crossed.cycle) def test_crossover_odd_length(self): cycle1 = "C" * 7 cycle2 = "D" * 7 - cross_cycle = "CCCDDDD" + cross_cycle = "CCDDDDD" - player1 = self.player_class(cycle=cycle1, seed=5) - player2 = self.player_class(cycle=cycle2) + player1 = self.player_class(cycle=cycle1, seed=6) + player2 = self.player_class(cycle=cycle2, seed=7) crossed = player1.crossover(player2) self.assertEqual(cross_cycle, crossed.cycle) diff --git a/axelrod/tests/strategies/test_geller.py b/axelrod/tests/strategies/test_geller.py index f201e1815..38491fe08 100644 --- a/axelrod/tests/strategies/test_geller.py +++ b/axelrod/tests/strategies/test_geller.py @@ -67,7 +67,7 @@ def test_returns_foil_inspection_strategy_of_opponent(self): self.versus_test(axl.Darwin(), expected_actions=[(C, C), (C, C), (C, C)], seed=3) self.versus_test( - axelrod.MindReader(), expected_actions=[(D, D), (D, D), (D, D)], seed=1 + axl.MindReader(), expected_actions=[(D, D), (D, D), (D, D)], seed=1 ) diff --git a/axelrod/tests/strategies/test_headsup.py b/axelrod/tests/strategies/test_headsup.py index 9b9282724..6bd375bc9 100644 --- a/axelrod/tests/strategies/test_headsup.py +++ b/axelrod/tests/strategies/test_headsup.py @@ -56,7 +56,7 @@ def test_rounds(self): self.versus_test( axl.ZDGTFT2(), axl.Bully(), - [C, D, D, C, C, C], + [C, D, D, C, C, D], [D, D, C, C, D, D], seed=2, ) @@ -69,9 +69,9 @@ def test_rounds(self): self.versus_test( axl.ZDExtort2(), axl.TitForTat(), - [C, D, D, D, D, D], - [C, C, D, D, D, D], - seed=2, + [C, D, C, D, D, D], + [C, C, D, C, D, D], + seed=100, ) diff --git a/axelrod/tests/strategies/test_hmm.py b/axelrod/tests/strategies/test_hmm.py index a5adea8e9..f1ace6be4 100644 --- a/axelrod/tests/strategies/test_hmm.py +++ b/axelrod/tests/strategies/test_hmm.py @@ -187,7 +187,7 @@ class TestEvolvedHMM5(TestPlayer): def test_strategy(self): actions = [(C, C), (C, D), (D, C), (D, D), (D, C)] - self.versus_test(opponent=axl.Alternator(), expected_actions=actions) + self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=2) class TestEvolvedHMM5vsCooperator(TestMatch): diff --git a/axelrod/tests/strategies/test_inverse.py b/axelrod/tests/strategies/test_inverse.py index 3eaee2a89..95ac95033 100644 --- a/axelrod/tests/strategies/test_inverse.py +++ b/axelrod/tests/strategies/test_inverse.py @@ -23,24 +23,15 @@ class TestInverse(TestPlayer): def test_strategy(self): # Test that as long as the opponent has not defected the player will - # cooperate. - self.versus_test(axl.Cooperator(), expected_actions=[(C, C)]) - - # Tests that if opponent has played all D then player chooses D. - self.versus_test(axl.Defector(), expected_actions=[(C, D)] + [(D, D)] * 9) - - expected_actions = [ - (C, D), - (D, C), - (D, C), - (D, D), - (D, C), - (C, C), - (C, C), - (C, C), - (C, D), - (D, D), - ] + # cooperate, regardless of the random seed. + self.versus_test(axl.Cooperator(), expected_actions=[(C, C)], seed=None) + + # Tests that if opponent has played all D then player chooses D, + # regardless of the random seed. + self.versus_test(axl.Defector(), expected_actions=[(C, D)] + [(D, D)] * 9, seed=None) + + expected_actions = [(C, D), (D, C), (D, C), (C, D), (D, C), (C, C), + (D, C), (C, C), (C, D), (D, D)] self.versus_test( axl.MockPlayer(actions=[a[1] for a in expected_actions]), expected_actions=expected_actions, diff --git a/axelrod/tests/strategies/test_memorytwo.py b/axelrod/tests/strategies/test_memorytwo.py index 32bc6b9d0..ac83e8012 100644 --- a/axelrod/tests/strategies/test_memorytwo.py +++ b/axelrod/tests/strategies/test_memorytwo.py @@ -153,7 +153,7 @@ class TestMemoryStochastic(TestPlayer): } def test_strategy(self): - rng = axelrod.RandomGenerator(seed=7888) + rng = axl.RandomGenerator(seed=7888) vector = [rng.random() for _ in range(16)] actions = [(C, C), (C, C), (D, D), (C, C), (D, C), (D, D), (D, C)] diff --git a/axelrod/tests/strategies/test_player.py b/axelrod/tests/strategies/test_player.py index e9f7488b6..31808a830 100644 --- a/axelrod/tests/strategies/test_player.py +++ b/axelrod/tests/strategies/test_player.py @@ -2,6 +2,8 @@ import itertools import pickle import types +import warnings + import numpy as np from hypothesis import given, settings @@ -452,45 +454,34 @@ def test_reset_clone(self): clone = player.clone() self.assertEqual(player, clone) - def test_reproducibility_of_play(self): - player = self.player() - player_clone = player.clone() - coplayer = axl.Random(0.5) - coplayer_clone = coplayer.clone() - m1 = axl.Match((player, coplayer), turns=10, seed=10) - m2 = axl.Match((player_clone, coplayer_clone), turns=10, seed=10) - m1.play() - m2.play() - self.assertEqual(m1.result, m2.result) - - @given(seed=integers(min_value=1, max_value=20000000)) - @settings(max_examples=1) - def test_clone(self, seed): + @given(seed=integers(min_value=1, max_value=20000000), + turns=integers(min_value=5, max_value=10), + noise=integers(min_value=0, max_value=10)) + @settings(max_examples=1, deadline=None) + def test_clone_reproducible_play(self, seed, turns, noise): # Test that the cloned player produces identical play - player1 = self.player() - if player1.name in ["Darwin", "Human"]: + player = self.player() + if player.name in ["Darwin", "Human"]: # Known exceptions return - player2 = player1.clone() - self.assertEqual(len(player2.history), 0) - self.assertEqual(player2.cooperations, 0) - self.assertEqual(player2.defections, 0) - self.assertEqual(player2.state_distribution, {}) - self.assertEqual(player2.classifier, player1.classifier) - self.assertEqual(player2.match_attributes, player1.match_attributes) - turns = 5 for op in [ axl.Cooperator(), axl.Defector(), axl.TitForTat(), axl.Random(p=0.5), ]: - for p in [player1, player2]: - m = axl.Match((p, op), turns=turns, reset=True, seed=seed) - m.play() - self.assertEqual(len(player1.history), turns) - self.assertEqual(player1.history, player2.history) + player = self.player() + player_clone = player.clone() + op = op.clone() + op_clone = op.clone() + m1 = axl.Match((player, op), turns=turns, seed=seed, noise=noise/100.) + m2 = axl.Match((player_clone, op_clone), turns=turns, seed=seed, noise=noise/100.) + m1.play() + m2.play() + self.assertEqual(m1.result, m2.result) + self.assertEqual(player, player_clone) + self.assertEqual(op, op_clone) @given( strategies=strategy_lists( @@ -582,6 +573,10 @@ def versus_test( match_attributes=match_attributes, seed=seed ) + if match._stochastic and (seed is None): + warnings.warn( + "Test Match in TestPlayer.versus_test is stochastic " + "but no random seed was given.") self.assertEqual(match.play(), expected_actions) if attrs: @@ -645,8 +640,12 @@ def versus_test( raise ValueError("Mismatched History lengths.") turns = len(expected_actions1) match = axl.Match((player1, player2), turns=turns, noise=noise, seed=seed) - match.play() - self.assertEqual(match.result, list(zip(expected_actions1, expected_actions2))) + if match._stochastic and (seed is None): + warnings.warn( + "Test Match in TestMatch.versus_test is stochastic " + "but no random seed was given.") + result = match.play() + self.assertEqual(result, list(zip(expected_actions1, expected_actions2))) def test_versus_with_incorrect_history_lengths(self): """Test the error raised by versus_test if expected actions do not diff --git a/axelrod/tests/strategies/test_rand.py b/axelrod/tests/strategies/test_rand.py index 76bfb3478..90562aaab 100644 --- a/axelrod/tests/strategies/test_rand.py +++ b/axelrod/tests/strategies/test_rand.py @@ -28,7 +28,7 @@ def test_strategy(self): self.versus_test(opponent, expected_actions=actions, seed=1) opponent = axl.MockPlayer() - actions = [(D, C), (D, C), (C, C)] + actions = [(D, C), (C, C), (D, C)] self.versus_test(opponent, expected_actions=actions, seed=2) opponent = axl.MockPlayer() diff --git a/axelrod/tests/strategies/test_titfortat.py b/axelrod/tests/strategies/test_titfortat.py index 43b631c6f..774adb031 100644 --- a/axelrod/tests/strategies/test_titfortat.py +++ b/axelrod/tests/strategies/test_titfortat.py @@ -482,8 +482,7 @@ def test_specific_set_of_results(self): axl.CyclerCCD(), axl.CyclerDDC(), ] - axl.seed(1) - tournament = axl.Tournament(players, turns=1000, repetitions=1) + tournament = axl.Tournament(players, turns=1000, repetitions=1, seed=1) results = tournament.play(progress_bar=False) scores = [ round(average_score_per_turn * 1000, 1) @@ -666,9 +665,8 @@ def test_output_from_literature(self): axl.WinStayLoseShift(), ] - axl.seed(1) turns = 1000 - tournament = axl.Tournament(players, turns=turns, repetitions=1) + tournament = axl.Tournament(players, turns=turns, repetitions=1, seed=1) results = tournament.play(progress_bar=False) scores = [ round(average_score_per_turn * 1000, 1) @@ -677,7 +675,7 @@ def test_output_from_literature(self): expected_scores = [ 3000.0, 915.0, - 2763.0, + 2756.0, 3000.0, 3000.0, 2219.0, @@ -740,21 +738,24 @@ def test_strategy_with_noise(self): self.assertEqual(opponent.history, [C]) # After noise: is contrite - player.play(opponent) + match = axl.Match((player, opponent), turns=2, noise=0.9, seed=9) + match.play() self.assertEqual(player.history, [D, C]) self.assertEqual(player._recorded_history, [C, C]) self.assertEqual(opponent.history, [C, D]) self.assertTrue(player.contrite) # Cooperates and no longer contrite - player.play(opponent) + match = axl.Match((player, opponent), turns=3, noise=0.9, seed=9) + match.play() self.assertEqual(player.history, [D, C, C]) self.assertEqual(player._recorded_history, [C, C, C]) self.assertEqual(opponent.history, [C, D, D]) self.assertFalse(player.contrite) # Goes back to playing tft - player.play(opponent) + match = axl.Match((player, opponent), turns=4, noise=0.9, seed=9) + match.play() self.assertEqual(player.history, [D, C, C, D]) self.assertEqual(player._recorded_history, [C, C, C, D]) self.assertEqual(opponent.history, [C, D, D, D]) From ca05063138e3c6cac96d6497e32a08d7b329c2b6 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Thu, 2 Jul 2020 11:34:14 -0700 Subject: [PATCH 010/121] Map TestPlayer.versus_test onto TestMatch. --- axelrod/tests/strategies/test_player.py | 59 +++++++++++++------------ 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/axelrod/tests/strategies/test_player.py b/axelrod/tests/strategies/test_player.py index 779053fdb..660b7b05f 100644 --- a/axelrod/tests/strategies/test_player.py +++ b/axelrod/tests/strategies/test_player.py @@ -563,9 +563,6 @@ def versus_test( Any noise to be passed to a match seed: int The random seed to be used - length: int - The length of the game. If `opponent` is a sequence of actions then - the length is taken to be the length of the sequence. match_attributes: dict The match attributes to be passed to the players. For example, `{length:-1}` implies that the players do not know the length of the @@ -577,27 +574,22 @@ def versus_test( A dictionary of keyword arguments to instantiate player with """ - turns = len(expected_actions) if init_kwargs is None: init_kwargs = dict() - if seed is not None: - axl.seed(seed) - player = self.player(**init_kwargs) - match = axl.Match( - (player, opponent), - turns=turns, + test_match = TestMatch() + test_match.versus_test( + player, + opponent, + [x for (x, y) in expected_actions], + [y for (x, y) in expected_actions], noise=noise, - match_attributes=match_attributes, + seed=seed, + attrs=attrs, + match_attributes=match_attributes ) - self.assertEqual(match.play(), expected_actions) - - if attrs: - player = match.players[0] - for attr, value in attrs.items(): - self.assertEqual(getattr(player, attr), value) def classifier_test(self, expected_class_classifier=None): """Test that the keys in the expected_classifier dictionary give the @@ -649,26 +641,35 @@ def versus_test( expected_actions2, noise=None, seed=None, + match_attributes=None, + attrs=None ): """Tests a sequence of outcomes for two given players.""" - if len(expected_actions1) != len(expected_actions2): - raise ValueError("Mismatched History lengths.") - if seed: + if seed is not None: axl.seed(seed) + if len(expected_actions1) != len(expected_actions2): + raise ValueError("Mismatched Expected History in TestMatch.") turns = len(expected_actions1) - match = axl.Match((player1, player2), turns=turns, noise=noise) + + match = axl.Match( + (player1, player2), turns=turns, noise=noise, + match_attributes=match_attributes) match.play() - # Test expected sequence of play. - for i, (outcome1, outcome2) in enumerate( - zip(expected_actions1, expected_actions2) - ): - player1.play(player2) - self.assertEqual(player1.history[i], outcome1) - self.assertEqual(player2.history[i], outcome2) + + # Test expected sequence of plays from the match is as expected. + for (play, expected_play) in zip(player1.history, expected_actions1): + self.assertEqual(play, expected_play) + for (play, expected_play) in zip(player2.history, expected_actions2): + self.assertEqual(play, expected_play) + + # Test final player attributes are as expected + if attrs: + for attr, value in attrs.items(): + self.assertEqual(getattr(player1, attr), value) def test_versus_with_incorrect_history_lengths(self): """Test the error raised by versus_test if expected actions do not - match up""" + match up.""" with self.assertRaises(ValueError): p1, p2 = axl.Cooperator(), axl.Cooperator() actions1 = [C, C] From 5195f310d4ef05c366f46eb282c9552c0f45d34b Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Thu, 2 Jul 2020 20:06:43 -0700 Subject: [PATCH 011/121] Add data classes and YAML serialization of test matches --- axelrod/__init__.py | 2 + axelrod/action.py | 4 +- axelrod/data_classes.py | 61 +++++++++++++++++++++++++ axelrod/tests/strategies/test_player.py | 5 +- axelrod/tests/unit/test_data_classes.py | 13 ++++++ axelrod/yaml.py | 59 ++++++++++++++++++++++++ 6 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 axelrod/data_classes.py create mode 100644 axelrod/tests/unit/test_data_classes.py create mode 100644 axelrod/yaml.py diff --git a/axelrod/__init__.py b/axelrod/__init__.py index 90eb959e1..dff5a9217 100644 --- a/axelrod/__init__.py +++ b/axelrod/__init__.py @@ -10,6 +10,8 @@ from axelrod.plot import Plot from axelrod.game import DefaultGame, Game from axelrod.history import History, LimitedHistory +from axelrod.data_classes import PlayerConfig +from axelrod.yaml import log_kwargs, load_matches from axelrod.player import Player from axelrod.classifier import Classifiers from axelrod.evolvable_player import EvolvablePlayer diff --git a/axelrod/action.py b/axelrod/action.py index a87b9a06b..a8ba85445 100644 --- a/axelrod/action.py +++ b/axelrod/action.py @@ -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): @@ -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 diff --git a/axelrod/data_classes.py b/axelrod/data_classes.py new file mode 100644 index 000000000..d3f0c3a09 --- /dev/null +++ b/axelrod/data_classes.py @@ -0,0 +1,61 @@ +from __future__ import annotations +from dataclasses import dataclass +from typing import List, Optional, Tuple + +import axelrod +from axelrod.action import Action + + +@dataclass +class PlayerConfig: + name: str + init_kwargs: dict = None + + def __call__(self): + # Look up player by name + player_class = getattr(axelrod, self.name) + if self.init_kwargs: + return player_class(**self.init_kwargs) + return player_class() + + +@dataclass +class MatchParameters: + turns: Optional[int] = None + noise: Optional[float] = None + prob_end: Optional[float] = None + seed: Optional[int] = None + game: Optional[axelrod.Game] = None + + +@dataclass +class ExpectedMatchOutcome: + player_actions: Tuple[Action, ...] + coplayer_actions: Tuple[Action, ...] + player_attributes: Optional[dict] = None + + +@dataclass +class MatchConfig: + player: PlayerConfig + coplayer: PlayerConfig + match_parameters: MatchParameters + expected_outcome: Optional[ExpectedMatchOutcome] = None + + def __call__(self): + """Generate the match.""" + player = self.player() + coplayer = self.coplayer() + noise = self.match_parameters.noise + prob_end = self.match_parameters.prob_end + turns = len(self.expected_outcome.player_actions) + match = axelrod.Match( + (player, coplayer), + turns=turns, + noise=noise, + prob_end=prob_end + ) + return match + + def test_match(self): + pass diff --git a/axelrod/tests/strategies/test_player.py b/axelrod/tests/strategies/test_player.py index 660b7b05f..1b820a2cd 100644 --- a/axelrod/tests/strategies/test_player.py +++ b/axelrod/tests/strategies/test_player.py @@ -7,7 +7,9 @@ import axelrod as axl import numpy as np from axelrod.player import simultaneous_play -from axelrod.tests.property import strategy_lists +from axelrod.tests.property import +from axelrod.yaml import log_kwargs + from hypothesis import given, settings from hypothesis.strategies import integers, sampled_from @@ -633,6 +635,7 @@ class TestMatch(unittest.TestCase): """Test class for heads up play between two given players. Plays an axelrod match between the two players.""" + @log_kwargs def versus_test( self, player1, diff --git a/axelrod/tests/unit/test_data_classes.py b/axelrod/tests/unit/test_data_classes.py new file mode 100644 index 000000000..223800c20 --- /dev/null +++ b/axelrod/tests/unit/test_data_classes.py @@ -0,0 +1,13 @@ +import unittest +import axelrod as axl + + +class TestPlayerConfig(unittest.TestCase): + + def test_call(self): + pc = axl.PlayerConfig("Cooperator") + player = pc() + print(player) + self.assertTrue(issubclass(type(player), axl.Player)) + self.assertTrue(isinstance(player, axl.Cooperator)) + diff --git a/axelrod/yaml.py b/axelrod/yaml.py new file mode 100644 index 000000000..edc4a9582 --- /dev/null +++ b/axelrod/yaml.py @@ -0,0 +1,59 @@ +from dataclasses import asdict +from typing import Tuple + +from dacite import from_dict, Config +import yaml + +import axelrod +from axelrod.action import Action, actions_to_str, str_to_actions +from axelrod.data_classes import ExpectedMatchOutcome, PlayerConfig, MatchConfig, MatchParameters + +filename = "test_matches.yaml" + + +def build_player_spec(name, init_kwargs=None): + return PlayerConfig(name=name, init_kwargs=dict(init_kwargs)) + + +def build_expected_spec(player_actions, coplayer_actions, attr=None): + return ExpectedMatchOutcome( + player_actions=player_actions, + coplayer_actions=coplayer_actions, + player_attributes=attr) + + +def build_match_parameters_spec(noise=None, seed=None): + return MatchParameters(noise=noise, seed=seed) + + +def build_match_spec(player_name, coplayer_name, player_actions, coplayer_actions, noise=None, seed=None, + player_init_kwargs=None, coplayer_init_kwargs=None, attr=None): + return MatchConfig( + player=build_player_spec(player_name, init_kwargs=player_init_kwargs.copy()), + coplayer=build_player_spec(coplayer_name,init_kwargs=coplayer_init_kwargs.copy()), + match_parameters=build_match_parameters_spec(noise=noise, seed=seed), + expected_outcome=build_expected_spec(player_actions, coplayer_actions, attr=attr) + ) + + +def log_kwargs(func): + def wrapper(*args, **kwargs): + stream = open('test_matches.yaml', 'a') + spec = build_match_spec(str(args[1].__class__.__name__), str(args[2].__class__.__name__), + actions_to_str(args[-2]), actions_to_str(args[-1]), + noise=kwargs["noise"], seed=kwargs["seed"], + player_init_kwargs=args[1].init_kwargs, + coplayer_init_kwargs=args[2].init_kwargs) + stream.write("---\n") + yaml.dump(asdict(spec), stream) + return func(*args, **kwargs) + return wrapper + + +def load_matches(): + stream = open(filename, 'r') + matches = yaml.load_all(stream, Loader=yaml.Loader) + return [from_dict(data_class=MatchConfig, data=match, config=Config( + type_hooks={Tuple[Action, ...]: str_to_actions})) for match in matches] + + From b910f942f27feafd0634f526c7ccceaf7d8bc48c Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Thu, 2 Jul 2020 20:16:29 -0700 Subject: [PATCH 012/121] Add dacite to requirements.txt --- axelrod/yaml.py | 4 +++- requirements.txt | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/axelrod/yaml.py b/axelrod/yaml.py index edc4a9582..95ff96e75 100644 --- a/axelrod/yaml.py +++ b/axelrod/yaml.py @@ -12,6 +12,8 @@ def build_player_spec(name, init_kwargs=None): + if name == "MockPlayer": + init_kwargs["actions"] = actions_to_str(init_kwargs["actions"]) return PlayerConfig(name=name, init_kwargs=dict(init_kwargs)) @@ -30,7 +32,7 @@ def build_match_spec(player_name, coplayer_name, player_actions, coplayer_action player_init_kwargs=None, coplayer_init_kwargs=None, attr=None): return MatchConfig( player=build_player_spec(player_name, init_kwargs=player_init_kwargs.copy()), - coplayer=build_player_spec(coplayer_name,init_kwargs=coplayer_init_kwargs.copy()), + coplayer=build_player_spec(coplayer_name, init_kwargs=coplayer_init_kwargs.copy()), match_parameters=build_match_parameters_spec(noise=noise, seed=seed), expected_outcome=build_expected_spec(player_actions, coplayer_actions, attr=attr) ) diff --git a/requirements.txt b/requirements.txt index 5c0d22cab..a56748d39 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,4 @@ pyyaml>=3.01 scipy>=0.19.0 toolz>=0.8.0 tqdm>=3.4.0 +dacite From 5823a63dcebdbac1dc40d6b589317d651f70d773 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Thu, 2 Jul 2020 20:59:37 -0700 Subject: [PATCH 013/121] Fix broken import --- axelrod/tests/strategies/test_player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axelrod/tests/strategies/test_player.py b/axelrod/tests/strategies/test_player.py index 1b820a2cd..a5bcf6d18 100644 --- a/axelrod/tests/strategies/test_player.py +++ b/axelrod/tests/strategies/test_player.py @@ -7,7 +7,7 @@ import axelrod as axl import numpy as np from axelrod.player import simultaneous_play -from axelrod.tests.property import +from axelrod.tests.property strategy_lists from axelrod.yaml import log_kwargs from hypothesis import given, settings From 9ffc176815a1f5e15c511561423f81c01d2255d7 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Thu, 2 Jul 2020 21:01:39 -0700 Subject: [PATCH 014/121] Fix broken import --- axelrod/tests/strategies/test_player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axelrod/tests/strategies/test_player.py b/axelrod/tests/strategies/test_player.py index a5bcf6d18..017945147 100644 --- a/axelrod/tests/strategies/test_player.py +++ b/axelrod/tests/strategies/test_player.py @@ -7,7 +7,7 @@ import axelrod as axl import numpy as np from axelrod.player import simultaneous_play -from axelrod.tests.property strategy_lists +from axelrod.tests.property import strategy_lists from axelrod.yaml import log_kwargs from hypothesis import given, settings From 3ee324f78e972835f676dd38551ff567a8e67689 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Thu, 2 Jul 2020 21:13:02 -0700 Subject: [PATCH 015/121] Fix test watching for headsup tests --- axelrod/yaml.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/axelrod/yaml.py b/axelrod/yaml.py index 95ff96e75..e6fa054dd 100644 --- a/axelrod/yaml.py +++ b/axelrod/yaml.py @@ -40,14 +40,24 @@ def build_match_spec(player_name, coplayer_name, player_actions, coplayer_action def log_kwargs(func): def wrapper(*args, **kwargs): - stream = open('test_matches.yaml', 'a') + try: + noise = kwargs["noise"] + except KeyError: + noise = None + try: + seed = kwargs["seed"] + except KeyError: + seed = None + spec = build_match_spec(str(args[1].__class__.__name__), str(args[2].__class__.__name__), actions_to_str(args[-2]), actions_to_str(args[-1]), - noise=kwargs["noise"], seed=kwargs["seed"], + noise=noise, seed=seed, player_init_kwargs=args[1].init_kwargs, coplayer_init_kwargs=args[2].init_kwargs) + stream = open(filename, 'a') stream.write("---\n") yaml.dump(asdict(spec), stream) + stream.close() return func(*args, **kwargs) return wrapper From d4284995fab5b252a9ac6ad63378e2d11f988507 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Thu, 2 Jul 2020 21:44:45 -0700 Subject: [PATCH 016/121] Rerun isort --- axelrod/moran.py | 3 +-- axelrod/player.py | 3 +-- axelrod/plot.py | 4 ++-- axelrod/random_.py | 2 +- axelrod/result_set.py | 2 +- axelrod/strategies/adaptor.py | 1 - axelrod/strategies/finite_state_machines.py | 1 - axelrod/strategies/hmm.py | 1 - axelrod/strategies/lookerup.py | 1 - axelrod/strategies/meta.py | 1 - axelrod/tests/strategies/test_adaptor.py | 1 + axelrod/tests/strategies/test_axelrod_second.py | 1 + axelrod/tests/strategies/test_hmm.py | 3 ++- axelrod/tests/strategies/test_player.py | 8 +++----- axelrod/tests/strategies/test_qlearner.py | 1 + axelrod/tests/strategies/test_titfortat.py | 5 ++--- axelrod/tests/unit/test_fingerprint.py | 6 ++---- axelrod/tests/unit/test_graph.py | 2 +- axelrod/tests/unit/test_history.py | 2 +- axelrod/tests/unit/test_interaction_utils.py | 2 +- axelrod/tests/unit/test_match.py | 5 ++--- axelrod/tests/unit/test_moran.py | 8 +++----- axelrod/tests/unit/test_plot.py | 5 ++--- axelrod/tests/unit/test_random_.py | 2 +- axelrod/tests/unit/test_resultset.py | 10 ++++------ axelrod/tests/unit/test_strategy_utils.py | 5 ++--- axelrod/tests/unit/test_tournament.py | 10 ++++------ axelrod/tournament.py | 3 +-- 28 files changed, 40 insertions(+), 58 deletions(-) diff --git a/axelrod/moran.py b/axelrod/moran.py index 1aaa8ea35..af8d18e57 100644 --- a/axelrod/moran.py +++ b/axelrod/moran.py @@ -6,11 +6,10 @@ import matplotlib.pyplot as plt import numpy as np from axelrod import DEFAULT_TURNS, EvolvablePlayer, Game, Player - from axelrod.deterministic_cache import DeterministicCache from axelrod.graph import Graph, complete_graph from axelrod.match import Match -from axelrod.random_ import RandomGenerator, BulkRandomGenerator +from axelrod.random_ import BulkRandomGenerator, RandomGenerator class MoranProcess(object): diff --git a/axelrod/player.py b/axelrod/player.py index 950a86b5e..0ac55d05d 100644 --- a/axelrod/player.py +++ b/axelrod/player.py @@ -2,11 +2,10 @@ import inspect import itertools import types -from typing import Any, Dict import warnings +from typing import Any, Dict import numpy as np - from axelrod import _module_random from axelrod.action import Action from axelrod.game import DefaultGame diff --git a/axelrod/plot.py b/axelrod/plot.py index 66e9838b8..f56cc9f4f 100644 --- a/axelrod/plot.py +++ b/axelrod/plot.py @@ -1,12 +1,12 @@ -from distutils.version import LooseVersion import pathlib +from distutils.version import LooseVersion from typing import List, Union import matplotlib import matplotlib.pyplot as plt import matplotlib.transforms as transforms -from numpy import arange, median, nan_to_num import tqdm +from numpy import arange, median, nan_to_num from .load_data_ import axl_filename from .result_set import ResultSet diff --git a/axelrod/random_.py b/axelrod/random_.py index ad2fd342a..39544007b 100644 --- a/axelrod/random_.py +++ b/axelrod/random_.py @@ -1,6 +1,6 @@ import numpy as np -from numpy.random import RandomState from axelrod.action import Action +from numpy.random import RandomState C, D = Action.C, Action.D diff --git a/axelrod/result_set.py b/axelrod/result_set.py index 4eef58683..65353cc6d 100644 --- a/axelrod/result_set.py +++ b/axelrod/result_set.py @@ -9,8 +9,8 @@ import dask.dataframe as dd import numpy as np import tqdm - from axelrod.action import Action + from . import eigen C, D = Action.C, Action.D diff --git a/axelrod/strategies/adaptor.py b/axelrod/strategies/adaptor.py index 6eb7e2961..a4bfcce65 100644 --- a/axelrod/strategies/adaptor.py +++ b/axelrod/strategies/adaptor.py @@ -2,7 +2,6 @@ from axelrod.action import Action from axelrod.player import Player - from numpy import heaviside C, D = Action.C, Action.D diff --git a/axelrod/strategies/finite_state_machines.py b/axelrod/strategies/finite_state_machines.py index db619397f..231f4a70b 100644 --- a/axelrod/strategies/finite_state_machines.py +++ b/axelrod/strategies/finite_state_machines.py @@ -9,7 +9,6 @@ ) from axelrod.player import Player - C, D = Action.C, Action.D actions = (C, D) Transition = Tuple[int, Action, int, Action] diff --git a/axelrod/strategies/hmm.py b/axelrod/strategies/hmm.py index b228e8401..4f425f36c 100644 --- a/axelrod/strategies/hmm.py +++ b/axelrod/strategies/hmm.py @@ -7,7 +7,6 @@ ) from axelrod.player import Player - C, D = Action.C, Action.D diff --git a/axelrod/strategies/lookerup.py b/axelrod/strategies/lookerup.py index 674ed7f0f..176398112 100644 --- a/axelrod/strategies/lookerup.py +++ b/axelrod/strategies/lookerup.py @@ -10,7 +10,6 @@ ) from axelrod.player import Player - C, D = Action.C, Action.D actions = (C, D) diff --git a/axelrod/strategies/meta.py b/axelrod/strategies/meta.py index cfd6de5f5..37b586b90 100644 --- a/axelrod/strategies/meta.py +++ b/axelrod/strategies/meta.py @@ -1,5 +1,4 @@ import numpy as np - from axelrod.action import Action from axelrod.classifier import Classifiers from axelrod.player import Player diff --git a/axelrod/tests/strategies/test_adaptor.py b/axelrod/tests/strategies/test_adaptor.py index dad77971d..c60e4939e 100644 --- a/axelrod/tests/strategies/test_adaptor.py +++ b/axelrod/tests/strategies/test_adaptor.py @@ -1,6 +1,7 @@ """Tests for the adaptor""" import axelrod as axl + from .test_player import TestPlayer C, D = axl.Action.C, axl.Action.D diff --git a/axelrod/tests/strategies/test_axelrod_second.py b/axelrod/tests/strategies/test_axelrod_second.py index d115d3098..760317b24 100644 --- a/axelrod/tests/strategies/test_axelrod_second.py +++ b/axelrod/tests/strategies/test_axelrod_second.py @@ -1,6 +1,7 @@ """Tests for the Second Axelrod strategies.""" import axelrod as axl + from .test_player import TestPlayer C, D = axl.Action.C, axl.Action.D diff --git a/axelrod/tests/strategies/test_hmm.py b/axelrod/tests/strategies/test_hmm.py index f1f0ca9fa..c9d5eac36 100644 --- a/axelrod/tests/strategies/test_hmm.py +++ b/axelrod/tests/strategies/test_hmm.py @@ -11,10 +11,11 @@ random_vector, ) ->>>>>>> upstream from .test_evolvable_player import PartialClass, TestEvolvablePlayer from .test_player import TestMatch, TestPlayer +>>>>>>> upstream + C, D = axl.Action.C, axl.Action.D diff --git a/axelrod/tests/strategies/test_player.py b/axelrod/tests/strategies/test_player.py index 4846c2fa4..bced268b8 100644 --- a/axelrod/tests/strategies/test_player.py +++ b/axelrod/tests/strategies/test_player.py @@ -1,17 +1,15 @@ import itertools import pickle import types -import warnings - import unittest +import warnings +import axelrod as axl import numpy as np +from axelrod.tests.property import strategy_lists from hypothesis import given, settings from hypothesis.strategies import integers, sampled_from -import axelrod as axl -from axelrod.tests.property import strategy_lists - C, D = axl.Action.C, axl.Action.D random = axl.RandomGenerator() short_run_time_short_mem = [ diff --git a/axelrod/tests/strategies/test_qlearner.py b/axelrod/tests/strategies/test_qlearner.py index c92b0312c..2c6de5e17 100644 --- a/axelrod/tests/strategies/test_qlearner.py +++ b/axelrod/tests/strategies/test_qlearner.py @@ -1,6 +1,7 @@ """Tests for the QLearner strategies.""" import axelrod as axl + from .test_player import TestPlayer C, D = axl.Action.C, axl.Action.D diff --git a/axelrod/tests/strategies/test_titfortat.py b/axelrod/tests/strategies/test_titfortat.py index 8feeb2661..9601b5117 100644 --- a/axelrod/tests/strategies/test_titfortat.py +++ b/axelrod/tests/strategies/test_titfortat.py @@ -1,11 +1,10 @@ """Tests for the tit for tat strategies.""" import copy -from hypothesis import settings -from hypothesis.strategies import integers import axelrod as axl from axelrod.tests.property import strategy_lists -from hypothesis import given +from hypothesis import given, settings +from hypothesis.strategies import integers from .test_player import TestPlayer diff --git a/axelrod/tests/unit/test_fingerprint.py b/axelrod/tests/unit/test_fingerprint.py index 49a3b878a..a82098e17 100644 --- a/axelrod/tests/unit/test_fingerprint.py +++ b/axelrod/tests/unit/test_fingerprint.py @@ -4,15 +4,13 @@ from tempfile import mkstemp from unittest.mock import patch -from hypothesis import given, settings +import axelrod as axl import matplotlib.pyplot import numpy as np - -import axelrod as axl from axelrod.fingerprint import AshlockFingerprint, Point, TransitiveFingerprint from axelrod.load_data_ import axl_filename from axelrod.tests.property import strategy_lists - +from hypothesis import given, settings C, D = axl.Action.C, axl.Action.D diff --git a/axelrod/tests/unit/test_graph.py b/axelrod/tests/unit/test_graph.py index e5a66d549..41f7d6d62 100644 --- a/axelrod/tests/unit/test_graph.py +++ b/axelrod/tests/unit/test_graph.py @@ -1,5 +1,5 @@ -from collections import defaultdict import unittest +from collections import defaultdict import axelrod as axl diff --git a/axelrod/tests/unit/test_history.py b/axelrod/tests/unit/test_history.py index 744d4f11a..e7b105c31 100644 --- a/axelrod/tests/unit/test_history.py +++ b/axelrod/tests/unit/test_history.py @@ -1,5 +1,5 @@ -from collections import Counter import unittest +from collections import Counter import axelrod as axl from axelrod.history import History, LimitedHistory diff --git a/axelrod/tests/unit/test_interaction_utils.py b/axelrod/tests/unit/test_interaction_utils.py index 6dfb3c3e2..49706fe01 100644 --- a/axelrod/tests/unit/test_interaction_utils.py +++ b/axelrod/tests/unit/test_interaction_utils.py @@ -1,6 +1,6 @@ -from collections import Counter import tempfile import unittest +from collections import Counter import axelrod as axl diff --git a/axelrod/tests/unit/test_match.py b/axelrod/tests/unit/test_match.py index 7ff61f62d..51aa291de 100644 --- a/axelrod/tests/unit/test_match.py +++ b/axelrod/tests/unit/test_match.py @@ -1,13 +1,12 @@ import unittest from collections import Counter -from hypothesis import example, given -from hypothesis.strategies import floats, integers - import axelrod as axl from axelrod.deterministic_cache import DeterministicCache from axelrod.random_ import RandomGenerator from axelrod.tests.property import games +from hypothesis import example, given +from hypothesis.strategies import floats, integers C, D = axl.Action.C, axl.Action.D diff --git a/axelrod/tests/unit/test_moran.py b/axelrod/tests/unit/test_moran.py index a2eba4cfb..8e05b53bc 100644 --- a/axelrod/tests/unit/test_moran.py +++ b/axelrod/tests/unit/test_moran.py @@ -2,14 +2,12 @@ import unittest from collections import Counter -from hypothesis import example, given, settings -from hypothesis.strategies import integers -import matplotlib.pyplot as plt - import axelrod as axl +import matplotlib.pyplot as plt from axelrod import MoranProcess from axelrod.tests.property import strategy_lists - +from hypothesis import example, given, settings +from hypothesis.strategies import integers C, D = axl.Action.C, axl.Action.D random = axl.RandomGenerator() diff --git a/axelrod/tests/unit/test_plot.py b/axelrod/tests/unit/test_plot.py index 5c6093e7d..847419035 100644 --- a/axelrod/tests/unit/test_plot.py +++ b/axelrod/tests/unit/test_plot.py @@ -2,12 +2,11 @@ import tempfile import unittest +import axelrod as axl import matplotlib import matplotlib.pyplot as plt -from numpy import mean - -import axelrod as axl from axelrod.load_data_ import axl_filename +from numpy import mean class TestPlot(unittest.TestCase): diff --git a/axelrod/tests/unit/test_random_.py b/axelrod/tests/unit/test_random_.py index dd88d8cf2..19a84c2b0 100644 --- a/axelrod/tests/unit/test_random_.py +++ b/axelrod/tests/unit/test_random_.py @@ -1,6 +1,6 @@ """Tests for the random functions.""" -from collections import Counter import unittest +from collections import Counter import axelrod as axl from axelrod import BulkRandomGenerator, Pdf, RandomGenerator diff --git a/axelrod/tests/unit/test_resultset.py b/axelrod/tests/unit/test_resultset.py index 6036b658d..c2239a2cd 100644 --- a/axelrod/tests/unit/test_resultset.py +++ b/axelrod/tests/unit/test_resultset.py @@ -3,16 +3,14 @@ import unittest from collections import Counter -from dask.dataframe.core import DataFrame -from hypothesis import given, settings -from numpy import mean, nanmedian, std -import pandas as pd - import axelrod as axl +import pandas as pd from axelrod.load_data_ import axl_filename from axelrod.result_set import create_counter_dict from axelrod.tests.property import prob_end_tournaments, tournaments - +from dask.dataframe.core import DataFrame +from hypothesis import given, settings +from numpy import mean, nanmedian, std C, D = axl.Action.C, axl.Action.D diff --git a/axelrod/tests/unit/test_strategy_utils.py b/axelrod/tests/unit/test_strategy_utils.py index 0dc3101d9..b3a4f7ef0 100644 --- a/axelrod/tests/unit/test_strategy_utils.py +++ b/axelrod/tests/unit/test_strategy_utils.py @@ -1,9 +1,6 @@ """Tests for the strategy utils.""" import unittest -from hypothesis import given, settings -from hypothesis.strategies import integers, lists, sampled_from - import axelrod as axl from axelrod._strategy_utils import ( detect_cycle, @@ -13,6 +10,8 @@ simulate_match, thue_morse_generator, ) +from hypothesis import given, settings +from hypothesis.strategies import integers, lists, sampled_from C, D = axl.Action.C, axl.Action.D diff --git a/axelrod/tests/unit/test_tournament.py b/axelrod/tests/unit/test_tournament.py index 9c98addef..86a822d4f 100644 --- a/axelrod/tests/unit/test_tournament.py +++ b/axelrod/tests/unit/test_tournament.py @@ -6,17 +6,12 @@ import pickle import unittest import warnings - from multiprocessing import Queue, cpu_count from unittest.mock import MagicMock, patch -from hypothesis import example, given, settings -from hypothesis.strategies import floats, integers +import axelrod as axl import numpy as np import pandas as pd -from tqdm import tqdm - -import axelrod as axl from axelrod.load_data_ import axl_filename from axelrod.tests.property import ( prob_end_tournaments, @@ -25,6 +20,9 @@ tournaments, ) from axelrod.tournament import _close_objects +from hypothesis import example, given, settings +from hypothesis.strategies import floats, integers +from tqdm import tqdm C, D = axl.Action.C, axl.Action.D diff --git a/axelrod/tournament.py b/axelrod/tournament.py index 2f301e31e..7e8f666ce 100644 --- a/axelrod/tournament.py +++ b/axelrod/tournament.py @@ -7,9 +7,8 @@ from tempfile import mkstemp from typing import List, Optional, Tuple -import tqdm - import axelrod.interaction_utils as iu +import tqdm from axelrod import DEFAULT_TURNS from axelrod.action import Action, actions_to_str from axelrod.player import Player From 35a5e3a1581f77411e384ded4ecc612fc21ccc9e Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Thu, 2 Jul 2020 21:45:16 -0700 Subject: [PATCH 017/121] Pin a new version of hypothesis --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b9e70133f..26f904216 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ cloudpickle>=0.2.1 fsspec>=0.4.3 dask>=2.3.0 -hypothesis>=4.0 +hypothesis=5.19 matplotlib>=2.0.0 numpy>=1.9.2 pandas>=0.18.1 From 3caa9c7d07a42807ca8302b891b1cd92af7df461 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 3 Jul 2020 10:55:44 -0700 Subject: [PATCH 018/121] Add set_seed function to Match --- axelrod/match.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/axelrod/match.py b/axelrod/match.py index c08977c5a..d4d043b9a 100644 --- a/axelrod/match.py +++ b/axelrod/match.py @@ -67,8 +67,7 @@ def __init__( self.result = [] self.noise = noise - self.seed = seed - self._random = RandomGenerator(seed=self.seed) + self.set_seed(seed) if game is None: self.game = Game() @@ -93,6 +92,10 @@ def __init__( self.players = list(players) self.reset = reset + def set_seed(self, seed): + self.seed = seed + self._random = RandomGenerator(seed=self.seed) + @property def players(self): return self._players From e19ac934c95db48b649957a1e296d8c98376cc99 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 3 Jul 2020 10:57:23 -0700 Subject: [PATCH 019/121] Rework test_adaptive --- axelrod/tests/strategies/test_adaptive.py | 58 +++++++++++++++-------- 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/axelrod/tests/strategies/test_adaptive.py b/axelrod/tests/strategies/test_adaptive.py index 188408d14..20befc067 100644 --- a/axelrod/tests/strategies/test_adaptive.py +++ b/axelrod/tests/strategies/test_adaptive.py @@ -21,27 +21,47 @@ class TestAdaptive(TestPlayer): "manipulates_state": False, } - def test_strategy(self): - actions = [(C, C)] * 6 + [(D, C)] * 8 - self.versus_test(axl.Cooperator(), expected_actions=actions) + def test_default_initial_actions_against_cooperator(self): + coplayer = axl.Cooperator() + player_actions = [C] * 6 + [D] * 8 + coplayer_actions = [C] * 14 + expected_actions = list(zip(player_actions, coplayer_actions)) + self.versus_test(coplayer, expected_actions=expected_actions) - actions = [(C, D)] * 6 + [(D, D)] * 8 - self.versus_test(axl.Defector(), expected_actions=actions) + def test_default_initial_actions_against_defector(self): + coplayer = axl.Defector() + player_actions = [C] * 6 + [D] * 8 + coplayer_actions = [D] * 14 + expected_actions = list(zip(player_actions, coplayer_actions)) + self.versus_test(coplayer, expected_actions=expected_actions) - actions = [(C, C), (C, D)] * 3 + [(D, C), (D, D)] * 4 - self.versus_test(axl.Alternator(), expected_actions=actions) + def test_default_initial_actions_against_alternator(self): + coplayer = axl.Alternator() + player_actions = [C] * 6 + [D] * 8 + coplayer_actions = [C, D] * 7 + expected_actions = list(zip(player_actions, coplayer_actions)) + self.versus_test(coplayer, expected_actions=expected_actions) - actions = [(C, C)] * 6 + [(D, C)] + [(D, D)] * 4 + [(C, D), (C, C)] - self.versus_test(axl.TitForTat(), expected_actions=actions) + def test_default_initial_actions_against_tft(self): + coplayer = axl.TitForTat() + player_actions = [C] * 6 + [D] * 5 + [C, C] + coplayer_actions = [C] * 7 + [D] * 5 + [C] + expected_actions = list(zip(player_actions, coplayer_actions)) + self.versus_test(coplayer, expected_actions=expected_actions) - def test_scoring(self): - player = axl.Adaptive() + def test_scoring_with_default_game(self): + """Tests that the default game is used in scoring.""" opponent = axl.Cooperator() - match = axl.Match((player, opponent), turns=2, seed=9) - match.play() - self.assertEqual(3, player.scores[C]) - - match = axl.Match((player, opponent), turns=1, reset=True, seed=9, - game=axl.Game(-3, 10, 10, 10)) - match.play() - self.assertEqual(0, player.scores[C]) + attrs = {"scores": {C: 3, D: 0}} + expected_actions = list(zip([C, C], [C, C])) + self.versus_test(opponent, expected_actions, turns=2, attrs=attrs, seed=9) + + def test_scoring_with_alternate_game(self): + """Tests that the alternate game is used in scoring.""" + player = axl.Adaptive() + opponent = axl.Alternator() + expected_actions = list(zip([C, C, C], [C, D, C])) + attrs = {"scores": {C: 7, D: 0}} + match_attributes = {"game": axl.Game(-3, 10, 10, 10)} + self.versus_test(opponent, expected_actions, turns=3, attrs=attrs, seed=9, + match_attributes=match_attributes) From 059106ce851a14a56479f3e49ebb1d12d3c0a478 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 3 Jul 2020 11:00:58 -0700 Subject: [PATCH 020/121] Rework test_adaptor --- axelrod/tests/strategies/test_adaptor.py | 25 +++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/axelrod/tests/strategies/test_adaptor.py b/axelrod/tests/strategies/test_adaptor.py index c60e4939e..ddaa28a06 100644 --- a/axelrod/tests/strategies/test_adaptor.py +++ b/axelrod/tests/strategies/test_adaptor.py @@ -20,35 +20,39 @@ class TestAdaptorBrief(TestPlayer): "manipulates_state": False, } - def test_strategy(self): + def test_strategy_no_error(self): # No error. actions = [(C, C), (C, C), (C, C), (C, C)] self.versus_test( - opponent=axl.AdaptorBrief(), expected_actions=actions, seed=0 + opponent=axl.AdaptorBrief(), expected_actions=actions, seed=1 ) + def test_strategy_error_corrected(self): # Error corrected. actions = [(C, C), (C, D), (D, C), (C, C)] self.versus_test( opponent=axl.AdaptorBrief(), expected_actions=actions, seed=245 ) + def test_strategy_error_corrected2(self): # Error corrected, example 2 actions = [(D, C), (C, D), (D, C), (C, D), (C, C)] self.versus_test( opponent=axl.AdaptorBrief(), expected_actions=actions, seed=7935 ) + def test_strategy_versus_cooperator(self): # Versus Cooperator actions = [(C, C)] * 8 self.versus_test( - opponent=axl.Cooperator(), expected_actions=actions, seed=0 + opponent=axl.Cooperator(), expected_actions=actions, seed=1 ) + def test_strategy_versus_defector(self): # Versus Defector actions = [(C, D), (D, D), (D, D), (D, D), (D, D), (D, D), (D, D)] self.versus_test( - opponent=axl.Defector(), expected_actions=actions, seed=0 + opponent=axl.Defector(), expected_actions=actions, seed=1 ) @@ -65,27 +69,30 @@ class TestAdaptorLong(TestPlayer): "manipulates_state": False, } - def test_strategy(self): + def test_strategy_no_error(self): # No error. actions = [(C, C), (C, C), (C, C), (C, C)] self.versus_test( - opponent=axl.AdaptorLong(), expected_actions=actions, seed=0 + opponent=axl.AdaptorLong(), expected_actions=actions, seed=1 ) + def test_strategy_error_corrected(self): # Error corrected. actions = [(C, C), (C, D), (D, D), (C, C), (C, C)] self.versus_test( - opponent=axl.AdaptorLong(), expected_actions=actions, seed=305 + opponent=axl.AdaptorLong(), expected_actions=actions, seed=245 ) + def test_strategy_versus_cooperator(self): # Versus Cooperator actions = [(C, C)] * 8 self.versus_test( - opponent=axl.Cooperator(), expected_actions=actions, seed=0 + opponent=axl.Cooperator(), expected_actions=actions, seed=1 ) + def test_strategy_versus_defector(self): # Versus Defector actions = [(C, D), (D, D), (C, D), (D, D), (D, D), (C, D), (D, D)] self.versus_test( - opponent=axl.Defector(), expected_actions=actions, seed=0 + opponent=axl.Defector(), expected_actions=actions, seed=1 ) From 9eac5a1feb7e70c9485abdb192cad4baea9931be Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 3 Jul 2020 11:04:48 -0700 Subject: [PATCH 021/121] Rework test_alternator --- axelrod/tests/strategies/test_alternator.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/axelrod/tests/strategies/test_alternator.py b/axelrod/tests/strategies/test_alternator.py index 64a347c33..937fd5618 100644 --- a/axelrod/tests/strategies/test_alternator.py +++ b/axelrod/tests/strategies/test_alternator.py @@ -21,13 +21,15 @@ class TestAlternator(TestPlayer): "manipulates_state": False, } - def test_strategy(self): + def test_versus_cooperator(self): actions = [(C, C), (D, C)] * 5 self.versus_test(axl.Cooperator(), expected_actions=actions) + def test_versus_defector(self): actions = [(C, D), (D, D)] * 5 self.versus_test(axl.Defector(), expected_actions=actions) - opponent = axl.MockPlayer(actions=[D, C]) + def test_versus_cycler_DC(self): + opponent = axl.Cycler(cycle="DC") actions = [(C, D), (D, C)] * 5 self.versus_test(opponent, expected_actions=actions) From 162814c557a7c1894a40df61a7b71cb18caf7f97 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 3 Jul 2020 11:08:47 -0700 Subject: [PATCH 022/121] Split tests in test_ann --- axelrod/tests/strategies/test_ann.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/axelrod/tests/strategies/test_ann.py b/axelrod/tests/strategies/test_ann.py index 6b02999ea..5c7c9235d 100644 --- a/axelrod/tests/strategies/test_ann.py +++ b/axelrod/tests/strategies/test_ann.py @@ -37,13 +37,15 @@ class TestEvolvedANN(TestPlayer): "manipulates_state": False, } - def test_strategy(self): + def test_strategy_versus_cooperator(self): actions = [(C, C)] * 5 self.versus_test(axl.Cooperator(), expected_actions=actions) + def test_strategy_versus_defector(self): actions = [(C, D)] + [(D, D)] * 5 self.versus_test(axl.Defector(), expected_actions=actions) + def test_strategy_versus_tft(self): actions = [(C, C)] * 5 self.versus_test(axl.TitForTat(), expected_actions=actions) @@ -62,10 +64,11 @@ class TestEvolvedANN5(TestPlayer): "manipulates_state": False, } - def test_strategy(self): + def test_strategy_versus_cooperator(self): actions = [(C, C)] * 5 self.versus_test(axl.Cooperator(), expected_actions=actions) + def test_strategy_versus_defector(self): actions = [(C, D)] + [(D, D)] * 4 self.versus_test(axl.Defector(), expected_actions=actions) @@ -84,10 +87,11 @@ class TestEvolvedANNNoise05(TestPlayer): "manipulates_state": False, } - def test_strategy(self): + def test_strategy_versus_cooperator(self): actions = [(C, C)] * 5 self.versus_test(axl.Cooperator(), expected_actions=actions) + def test_strategy_versus_defector(self): actions = [(C, D), (D, D), (D, D)] self.versus_test(axl.Defector(), expected_actions=actions) From 585148b947b8adbf926a846003f7a5e5b41700d6 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 3 Jul 2020 11:31:05 -0700 Subject: [PATCH 023/121] Split tests in test_apavlov --- axelrod/tests/strategies/test_apavlov.py | 31 ++++++++++++++++++++---- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/axelrod/tests/strategies/test_apavlov.py b/axelrod/tests/strategies/test_apavlov.py index e720dce56..a532df34f 100644 --- a/axelrod/tests/strategies/test_apavlov.py +++ b/axelrod/tests/strategies/test_apavlov.py @@ -21,7 +21,7 @@ class TestAPavlov2006(TestPlayer): "manipulates_state": False, } - def test_strategy(self): + def test_strategy_versus_cooperator(self): actions = [(C, C)] * 7 self.versus_test( axl.Cooperator(), @@ -29,12 +29,16 @@ def test_strategy(self): attrs={"opponent_class": "Cooperative"}, ) + def test_strategy_versus_mock_player(self): + """Tests that one defection after does not affect opponent_class determination.""" opponent = axl.MockPlayer(actions=[C] * 6 + [D]) actions = [(C, C)] * 6 + [(C, D), (D, C)] self.versus_test( opponent, expected_actions=actions, attrs={"opponent_class": "Cooperative"} ) + def test_strategy_versus_defector(self): + """Tests that defector is recognized correctly.""" actions = [(C, D)] + [(D, D)] * 6 self.versus_test( axl.Defector(), @@ -42,7 +46,9 @@ def test_strategy(self): attrs={"opponent_class": "ALLD"}, ) - opponent = axl.MockPlayer(actions=[D, C, D, C, D, C]) + def test_strategy_stft(self): + """Tests that STFT can be identified by DCDCDC and the subsequent response.""" + opponent = axl.CyclerDC() actions = [ (C, D), (D, C), @@ -59,24 +65,31 @@ def test_strategy(self): opponent, expected_actions=actions, attrs={"opponent_class": "STFT"} ) - opponent = axl.MockPlayer(actions=[D, D, C, D, D, C]) + def test_strategy_PavlovD(self): + """Tests that PavolvD is identified by DDCDDC.""" + opponent = axl.Cycler(cycle="DDC") actions = [(C, D), (D, D), (D, C), (C, D), (D, D), (D, C), (D, D)] self.versus_test( opponent, expected_actions=actions, attrs={"opponent_class": "PavlovD"} ) + def test_strategy_PavlovD(self): + """Tests that PavolvD is identified by DDCDDC and that the response + is D then C""" opponent = axl.MockPlayer(actions=[D, D, C, D, D, C, D]) actions = [(C, D), (D, D), (D, C), (C, D), (D, D), (D, C), (D, D), (C, D)] self.versus_test( opponent, expected_actions=actions, attrs={"opponent_class": "PavlovD"} ) + def test_strategy_random(self): opponent = axl.MockPlayer(actions=[C, C, C, D, D, D]) actions = [(C, C), (C, C), (C, C), (C, D), (D, D), (D, D), (D, C)] self.versus_test( opponent, expected_actions=actions, attrs={"opponent_class": "Random"} ) + def test_strategy_random2(self): opponent = axl.MockPlayer(actions=[D, D, D, C, C, C]) actions = [(C, D), (D, D), (D, D), (D, C), (C, C), (C, C), (D, D)] self.versus_test( @@ -98,8 +111,7 @@ class TestAPavlov2011(TestPlayer): "manipulates_state": False, } - def test_strategy(self): - + def test_strategy_versus_cooperator(self): actions = [(C, C)] * 8 self.versus_test( axl.Cooperator(), @@ -107,6 +119,7 @@ def test_strategy(self): attrs={"opponent_class": "Cooperative"}, ) + def test_strategy_versus_defector(self): actions = [(C, D)] + [(D, D)] * 9 self.versus_test( axl.Defector(), @@ -114,48 +127,56 @@ def test_strategy(self): attrs={"opponent_class": "ALLD"}, ) + def test_strategy_versus_defector2(self): opponent = axl.MockPlayer(actions=[C, D, D, D, D, D, D]) actions = [(C, C), (C, D)] + [(D, D)] * 5 + [(D, C)] self.versus_test( opponent, expected_actions=actions, attrs={"opponent_class": "ALLD"} ) + def test_strategy_versus_defector3(self): opponent = axl.MockPlayer(actions=[C, C, D, D, D, D, D]) actions = [(C, C), (C, C), (C, D)] + [(D, D)] * 4 + [(D, C)] self.versus_test( opponent, expected_actions=actions, attrs={"opponent_class": "ALLD"} ) + def test_strategy_versus_defector4(self): opponent = axl.MockPlayer(actions=[C, D, D, C, D, D, D]) actions = [(C, C), (C, D), (D, D), (D, C), (C, D), (D, D), (D, D), (D, C)] self.versus_test( opponent, expected_actions=actions, attrs={"opponent_class": "ALLD"} ) + def test_strategy_stft(self): opponent = axl.MockPlayer(actions=[C, D, D, C, C, D, D]) actions = [(C, C), (C, D), (D, D), (D, C), (C, C), (C, D), (C, D), (D, C)] self.versus_test( opponent, expected_actions=actions, attrs={"opponent_class": "STFT"} ) + def test_strategy_versus_stft2(self): opponent = axl.MockPlayer(actions=[C, D, C, D, C, D, D]) actions = [(C, C), (C, D), (D, C), (C, D), (D, C), (C, D), (C, D), (D, C)] self.versus_test( opponent, expected_actions=actions, attrs={"opponent_class": "STFT"} ) + def test_strategy_versus_stft3(self): opponent = axl.MockPlayer(actions=[D, D, D, C, C, C, C]) actions = [(C, D), (D, D), (D, D), (D, C), (C, C), (C, C), (C, C), (C, D)] self.versus_test( opponent, expected_actions=actions, attrs={"opponent_class": "STFT"} ) + def test_strategy_versus_random(self): opponent = axl.MockPlayer(actions=[C, C, C, C, D, D]) actions = [(C, C), (C, C), (C, C), (C, C), (C, D), (D, D), (D, C), (D, C)] self.versus_test( opponent, expected_actions=actions, attrs={"opponent_class": "Random"} ) + def test_strategy_versus_random2(self): opponent = axl.MockPlayer(actions=[D, D, C, C, C, C]) actions = [(C, D), (D, D), (D, C), (C, C), (C, C), (C, C), (D, D), (D, D)] self.versus_test( From 0d24c3e61cb318bd99146622013df2e0ca9d9192 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 3 Jul 2020 12:56:36 -0700 Subject: [PATCH 024/121] Split tests in test_appeaser --- axelrod/tests/strategies/test_appeaser.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/axelrod/tests/strategies/test_appeaser.py b/axelrod/tests/strategies/test_appeaser.py index ede79c1d5..54b550a0e 100644 --- a/axelrod/tests/strategies/test_appeaser.py +++ b/axelrod/tests/strategies/test_appeaser.py @@ -21,17 +21,20 @@ class TestAppeaser(TestPlayer): "manipulates_state": False, } - def test_strategy(self): + def test_strategy_versus_cooperator(self): actions = [(C, C), (C, C), (C, C), (C, C), (C, C)] self.versus_test(axl.Cooperator(), expected_actions=actions) + def test_strategy_versus_defector(self): actions = [(C, D), (D, D), (C, D), (D, D), (C, D)] self.versus_test(axl.Defector(), expected_actions=actions) + def test_cooperate_on_opponent_defect(self): opponent = axl.MockPlayer(actions=[C, C, D, D]) actions = [(C, C), (C, C), (C, D), (D, D), (C, C), (C, C)] self.versus_test(opponent, expected_actions=actions) + def test_cooperate_then_defect_on_opponent_defect(self): opponent = axl.MockPlayer(actions=[C, C, D, D, D]) actions = [(C, C), (C, C), (C, D), (D, D), (C, D), (D, C), (D, C)] self.versus_test(opponent, expected_actions=actions) From 84db8e9e8b96cc61a527e9e2525c542cba90692b Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 3 Jul 2020 19:29:52 -0700 Subject: [PATCH 025/121] Rework test_averagecopier --- .../tests/strategies/test_averagecopier.py | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/axelrod/tests/strategies/test_averagecopier.py b/axelrod/tests/strategies/test_averagecopier.py index 965990a30..77cbe9ef0 100644 --- a/axelrod/tests/strategies/test_averagecopier.py +++ b/axelrod/tests/strategies/test_averagecopier.py @@ -21,21 +21,26 @@ class TestAverageCopier(TestPlayer): "manipulates_state": False, } - def test_strategy(self): - # Tests that if opponent has played all C then player chooses C. + def test_cooperate_if_opponent_always_cooperates1(self): + """Tests that if opponent has played all C then player chooses C.""" actions = [(C, C)] * 10 self.versus_test(axl.Cooperator(), expected_actions=actions, seed=1) + + def test_cooperate_if_opponent_always_cooperates2(self): actions = [(D, C)] + [(C, C)] * 9 self.versus_test(axl.Cooperator(), expected_actions=actions, seed=2) - # Tests that if opponent has played all D then player chooses D. + def test_defect_if_opponent_always_defects1(self): + """Tests that if opponent has played all D then player chooses D.""" actions = [(C, D)] + [(D, D)] * 9 self.versus_test(axl.Defector(), expected_actions=actions, seed=1) + + def test_defect_if_opponent_always_defects2(self): actions = [(D, D)] + [(D, D)] * 9 self.versus_test(axl.Defector(), expected_actions=actions, seed=2) + def test_random_behavior1(self): # Variable behaviour based on the history and stochastic - actions = [ (C, C), (C, D), @@ -50,6 +55,7 @@ def test_strategy(self): ] self.versus_test(axl.Alternator(), expected_actions=actions, seed=1) + def test_random_behavior2(self): actions = [ (D, C), (C, D), @@ -64,6 +70,7 @@ def test_strategy(self): ] self.versus_test(axl.Alternator(), expected_actions=actions, seed=2) + def test_random_behavior3(self): opponent = axl.MockPlayer(actions=[C, C, D, D, D, D]) actions = [ (C, C), @@ -79,6 +86,7 @@ def test_strategy(self): ] self.versus_test(opponent, expected_actions=actions, seed=1) + def test_random_behavior4(self): opponent = axl.MockPlayer(actions=[C, C, C, D, D, D]) actions = [ (D, C), @@ -109,15 +117,17 @@ class TestNiceAverageCopier(TestPlayer): "manipulates_state": False, } - def test_strategy(self): + def test_cooperate_if_opponent_always_cooperates(self): # Tests that if opponent has played all C then player chooses C. actions = [(C, C)] * 10 self.versus_test(axl.Cooperator(), expected_actions=actions, seed=1) + def test_defect_if_opponent_always_defects(self): # Tests that if opponent has played all D then player chooses D. actions = [(C, D)] + [(D, D)] * 9 self.versus_test(axl.Defector(), expected_actions=actions, seed=1) + def test_random_behavior1(self): # Variable behaviour based on the history and stochastic behaviour actions = [ (C, C), @@ -133,6 +143,7 @@ def test_strategy(self): ] self.versus_test(axl.Alternator(), expected_actions=actions, seed=1) + def test_random_behavior2(self): actions = [ (C, C), (C, D), @@ -147,6 +158,7 @@ def test_strategy(self): ] self.versus_test(axl.Alternator(), expected_actions=actions, seed=2) + def test_random_behavior3(self): opponent = axl.MockPlayer(actions=[C, C, D, D, D, D]) actions = [ (C, C), @@ -162,6 +174,7 @@ def test_strategy(self): ] self.versus_test(opponent, expected_actions=actions, seed=1) + def test_random_behavior4(self): opponent = axl.MockPlayer(actions=[C, C, C, D, D, D]) actions = [ (C, C), From 28f7e82115b7762e26647546f015f4f2d3942b73 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 3 Jul 2020 19:43:23 -0700 Subject: [PATCH 026/121] Rename some tests in test_apavlov --- axelrod/tests/strategies/test_apavlov.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/axelrod/tests/strategies/test_apavlov.py b/axelrod/tests/strategies/test_apavlov.py index a532df34f..3641a0d87 100644 --- a/axelrod/tests/strategies/test_apavlov.py +++ b/axelrod/tests/strategies/test_apavlov.py @@ -111,7 +111,7 @@ class TestAPavlov2011(TestPlayer): "manipulates_state": False, } - def test_strategy_versus_cooperator(self): + def test_strategy_cooperator(self): actions = [(C, C)] * 8 self.versus_test( axl.Cooperator(), @@ -119,7 +119,7 @@ def test_strategy_versus_cooperator(self): attrs={"opponent_class": "Cooperative"}, ) - def test_strategy_versus_defector(self): + def test_strategy_defector(self): actions = [(C, D)] + [(D, D)] * 9 self.versus_test( axl.Defector(), @@ -127,21 +127,21 @@ def test_strategy_versus_defector(self): attrs={"opponent_class": "ALLD"}, ) - def test_strategy_versus_defector2(self): + def test_strategy_defector2(self): opponent = axl.MockPlayer(actions=[C, D, D, D, D, D, D]) actions = [(C, C), (C, D)] + [(D, D)] * 5 + [(D, C)] self.versus_test( opponent, expected_actions=actions, attrs={"opponent_class": "ALLD"} ) - def test_strategy_versus_defector3(self): + def test_strategy_defector3(self): opponent = axl.MockPlayer(actions=[C, C, D, D, D, D, D]) actions = [(C, C), (C, C), (C, D)] + [(D, D)] * 4 + [(D, C)] self.versus_test( opponent, expected_actions=actions, attrs={"opponent_class": "ALLD"} ) - def test_strategy_versus_defector4(self): + def test_strategy_defector4(self): opponent = axl.MockPlayer(actions=[C, D, D, C, D, D, D]) actions = [(C, C), (C, D), (D, D), (D, C), (C, D), (D, D), (D, D), (D, C)] self.versus_test( @@ -155,28 +155,28 @@ def test_strategy_stft(self): opponent, expected_actions=actions, attrs={"opponent_class": "STFT"} ) - def test_strategy_versus_stft2(self): + def test_strategy_stft2(self): opponent = axl.MockPlayer(actions=[C, D, C, D, C, D, D]) actions = [(C, C), (C, D), (D, C), (C, D), (D, C), (C, D), (C, D), (D, C)] self.versus_test( opponent, expected_actions=actions, attrs={"opponent_class": "STFT"} ) - def test_strategy_versus_stft3(self): + def test_strategy_stft3(self): opponent = axl.MockPlayer(actions=[D, D, D, C, C, C, C]) actions = [(C, D), (D, D), (D, D), (D, C), (C, C), (C, C), (C, C), (C, D)] self.versus_test( opponent, expected_actions=actions, attrs={"opponent_class": "STFT"} ) - def test_strategy_versus_random(self): + def test_strategy_random(self): opponent = axl.MockPlayer(actions=[C, C, C, C, D, D]) actions = [(C, C), (C, C), (C, C), (C, C), (C, D), (D, D), (D, C), (D, C)] self.versus_test( opponent, expected_actions=actions, attrs={"opponent_class": "Random"} ) - def test_strategy_versus_random2(self): + def test_strategy_random2(self): opponent = axl.MockPlayer(actions=[D, D, C, C, C, C]) actions = [(C, D), (D, D), (D, C), (C, C), (C, C), (C, C), (D, D), (D, D)] self.versus_test( From 77a7587da0382d4ea719cb387fc2f6c8826b7124 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 3 Jul 2020 19:45:01 -0700 Subject: [PATCH 027/121] Change opponent in test_alternator --- axelrod/tests/strategies/test_alternator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axelrod/tests/strategies/test_alternator.py b/axelrod/tests/strategies/test_alternator.py index 937fd5618..98ecde394 100644 --- a/axelrod/tests/strategies/test_alternator.py +++ b/axelrod/tests/strategies/test_alternator.py @@ -30,6 +30,6 @@ def test_versus_defector(self): self.versus_test(axl.Defector(), expected_actions=actions) def test_versus_cycler_DC(self): - opponent = axl.Cycler(cycle="DC") + opponent = axl.CyclerDC actions = [(C, D), (D, C)] * 5 self.versus_test(opponent, expected_actions=actions) From 8615f1d6c8eb6ff4605d7550c8efc574458a0448 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 3 Jul 2020 19:59:05 -0700 Subject: [PATCH 028/121] New seeds for test_gambler --- axelrod/tests/strategies/test_gambler.py | 97 +++++++++++++++--------- 1 file changed, 63 insertions(+), 34 deletions(-) diff --git a/axelrod/tests/strategies/test_gambler.py b/axelrod/tests/strategies/test_gambler.py index bc46dbf89..db645de95 100755 --- a/axelrod/tests/strategies/test_gambler.py +++ b/axelrod/tests/strategies/test_gambler.py @@ -15,6 +15,7 @@ C, D = axl.Action.C, axl.Action.D random = axl.RandomGenerator() + class TestGambler(TestPlayer): name = "Gambler" @@ -47,7 +48,7 @@ def test_stochastic_values(self): axl.Cooperator(), expected_actions=expected_actions, init_kwargs={"lookup_dict": stochastic_lookup}, - seed=1, + seed=80, ) @@ -82,13 +83,12 @@ def test_strategy(self): self.versus_test(axl.Cooperator(), expected_actions=vs_cooperator) def test_defects_forever_with_correct_conditions(self): - seed = 1 opponent_actions = [D, D] + [C] * 10 expected = [(C, D), (C, D), (D, C)] + [(D, C)] * 9 self.versus_test( axl.MockPlayer(actions=opponent_actions), expected_actions=expected, - seed=seed, + seed=1, ) @@ -122,25 +122,24 @@ def test_new_data(self): self.assertEqual(self.player().lookup_dict, converted_original) def test_cooperate_forever(self): - seed = 2 opponent = [D] * 3 + [C] * 10 expected = [(C, D), (D, D), (D, D)] + [(C, C)] * 10 self.versus_test( - axl.MockPlayer(opponent), expected_actions=expected, seed=seed + axl.MockPlayer(opponent), expected_actions=expected, seed=4 ) def test_defect_forever(self): - seed = 2 opponent_actions = [C] + [D] + [C] * 10 expected = [(C, C), (C, D)] + [(D, C)] * 10 self.versus_test( - axl.MockPlayer(opponent_actions), expected_actions=expected, seed=seed + axl.MockPlayer(opponent_actions), expected_actions=expected, seed=2 ) + def test_defect_forever2(self): opponent_actions = [D] + [C] * 10 expected = [(C, D)] + [(D, C)] * 10 self.versus_test( - axl.MockPlayer(opponent_actions), expected_actions=expected, seed=seed + axl.MockPlayer(opponent_actions), expected_actions=expected, seed=4 ) @@ -238,12 +237,10 @@ def test_vs_cooperator(self): self.versus_test(axl.Cooperator(), expected_actions=expected) def test_vs_alternator(self): - seed = 1 expected = [(C, C), (C, D), (C, C), (D, D), (D, C), (D, D), (D, C)] - self.versus_test(axl.Alternator(), expected_actions=expected, seed=seed) + self.versus_test(axl.Alternator(), expected_actions=expected, seed=20) def test_vs_DCDDC(self): - seed = 2 opponent_actions = [D, C, D, D, C] expected = [ (C, D), @@ -260,15 +257,28 @@ def test_vs_DCDDC(self): self.versus_test( axl.MockPlayer(actions=opponent_actions), expected_actions=expected, - seed=seed, + seed=2, ) - new_seed = 139 # First seed with different result. + def test_vs_DCDDC2(self): + opponent_actions = [D, C, D, D, C] + expected = [ + (C, D), + (C, C), + (D, D), + (D, D), + (C, C), + (D, D), + (D, C), + (D, D), + (D, D), + (C, C), + ] expected[5] = (C, D) self.versus_test( axl.MockPlayer(actions=opponent_actions), expected_actions=expected, - seed=new_seed, + seed=531, ) @@ -365,19 +375,17 @@ def test_vs_cooperator(self): self.versus_test(axl.Cooperator(), expected_actions=expected) def test_vs_alternator(self): - seed = 2 expected = [(C, C), (C, D), (C, C), (D, D), (D, C), (D, D), (C, C)] - self.versus_test(axl.Alternator(), expected_actions=expected, seed=seed) + self.versus_test(axl.Alternator(), expected_actions=expected, seed=2) - new_seed = 1 + def test_vs_alternator2(self): + expected = [(C, C), (C, D), (C, C), (D, D), (D, C), (D, D), (C, C)] expected[4] = (C, C) expected[6] = (D, C) - self.versus_test(axl.Alternator(), expected_actions=expected, seed=new_seed) + self.versus_test(axl.Alternator(), expected_actions=expected, seed=3) def test_vs_DCDDC(self): opponent_actions = [D, C, D, D, C] - - seed = 1 expected = [ (C, D), (C, C), @@ -390,23 +398,47 @@ def test_vs_DCDDC(self): (C, D), ] self.versus_test( - axl.MockPlayer(opponent_actions), expected_actions=expected, seed=seed + axl.MockPlayer(opponent_actions), expected_actions=expected, seed=1 ) - new_seed = 3 - expected[8] = (D, D) + def test_vs_DCDDC2(self): + opponent_actions = [D, C, D, D, C] + expected = [ + (C, D), + (C, C), + (D, D), + (D, D), + (C, C), + (D, D), + (D, C), + (C, D), + (D, D), # different than above test + ] self.versus_test( axl.MockPlayer(opponent_actions), expected_actions=expected, - seed=new_seed, + seed=5, ) - new_seed = 2 + def test_vs_DCDDC3(self): + opponent_actions = [D, C, D, D, C] + expected = [ + (C, D), + (C, C), + (D, D), + (D, D), + (C, C), + (D, D), + (D, C), + (C, C), # different than above test + (D, D), # different than above test + (D, D), # different than above test + ] new_expected = expected[:6] + [(C, C), (D, D), (D, D)] self.versus_test( axl.MockPlayer(opponent_actions), expected_actions=new_expected, - seed=new_seed, + seed=10, ) @@ -447,7 +479,6 @@ def test_new_data(self): self.assertEqual(self.player().lookup_dict, converted_original) def test_vs_defector(self): - seed = 5 expected = [ (C, D), (C, D), @@ -461,10 +492,9 @@ def test_vs_defector(self): (D, D), ] - self.versus_test(axl.Defector(), expected_actions=expected, seed=seed) + self.versus_test(axl.Defector(), expected_actions=expected, seed=30) def test_vs_cooperator(self): - seed = 5 expected = [ (C, C), (C, C), @@ -478,16 +508,15 @@ def test_vs_cooperator(self): (C, C), ] - self.versus_test(axl.Cooperator(), expected_actions=expected, seed=seed) + self.versus_test(axl.Cooperator(), expected_actions=expected, seed=33) def test_vs_alternator(self): - seed = 2 expected = [(C, C), (C, D), (D, C), (D, D), (C, C), (C, D), (D, C)] - self.versus_test(axl.Alternator(), expected_actions=expected, seed=seed) + self.versus_test(axl.Alternator(), expected_actions=expected, seed=42) - new_seed = 1 + def test_vs_alternator2(self): expected = [(C, C), (C, D), (C, C), (D, D), (D, C), (C, D), (D, C)] - self.versus_test(axl.Alternator(), expected_actions=expected, seed=new_seed) + self.versus_test(axl.Alternator(), expected_actions=expected, seed=67) class TestEvolvableGambler(unittest.TestCase): From d784afe46f2c139440bc014e1a9fffabf52ed5cc Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 3 Jul 2020 20:01:26 -0700 Subject: [PATCH 029/121] Fix test_hmm --- axelrod/tests/strategies/test_hmm.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/axelrod/tests/strategies/test_hmm.py b/axelrod/tests/strategies/test_hmm.py index c9d5eac36..a5df3f9fe 100644 --- a/axelrod/tests/strategies/test_hmm.py +++ b/axelrod/tests/strategies/test_hmm.py @@ -8,13 +8,11 @@ HMMPlayer, SimpleHMM, is_stochastic_matrix, - random_vector, ) from .test_evolvable_player import PartialClass, TestEvolvablePlayer from .test_player import TestMatch, TestPlayer ->>>>>>> upstream C, D = axl.Action.C, axl.Action.D From a39a78b160b86dc3be6ede16790cb0fe2452a245 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 3 Jul 2020 20:06:07 -0700 Subject: [PATCH 030/121] Fixseeds for test_memoryone --- axelrod/tests/strategies/test_memoryone.py | 29 +++++++++++++++------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/axelrod/tests/strategies/test_memoryone.py b/axelrod/tests/strategies/test_memoryone.py index 5b8c5f05c..93c9abc04 100644 --- a/axelrod/tests/strategies/test_memoryone.py +++ b/axelrod/tests/strategies/test_memoryone.py @@ -68,8 +68,9 @@ class TestGTFT(TestPlayer): def test_strategy(self): actions = [(C, C), (C, D), (D, C), (C, D), (D, C)] - self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=0) + self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=2) + def test_strategy2(self): actions = [(C, C), (C, D), (C, C), (C, D), (D, C)] self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=1) @@ -104,13 +105,14 @@ def test_four_vector(self): test_four_vector(self, expected_dictionary) def test_strategy(self): - actions = [(C, C), (C, D), (D, C), (C, D), (D, C)] self.versus_test(opponent=axl.Alternator(), expected_actions=actions) + def test_strategy2(self): actions = [(C, D), (D, D), (D, D), (D, D), (C, D)] - self.versus_test(opponent=axl.Defector(), expected_actions=actions, seed=0) + self.versus_test(opponent=axl.Defector(), expected_actions=actions, seed=10) + def test_strategy3(self): actions = [(C, D), (D, D), (C, D), (D, D), (D, D)] self.versus_test(opponent=axl.Defector(), expected_actions=actions, seed=1) @@ -140,16 +142,19 @@ def test_four_vector(self): def test_strategy(self): actions = [(C, C), (D, D), (C, C), (C, D), (C, C), (D, D)] - self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=15) + self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=113) + def test_strategy2(self): actions = [(C, C), (C, D), (D, C), (D, D), (C, C), (C, D)] self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=1) + def test_strategy3(self): actions = [(C, C), (C, D), (D, C), (D, D), (D, C), (D, D)] - self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=3) + self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=5) + def test_strategy4(self): actions = [(C, C), (C, D), (D, C), (D, D), (D, C), (C, D)] - self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=13) + self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=12) class TestStochasticWSLS(TestPlayer): @@ -168,16 +173,19 @@ class TestStochasticWSLS(TestPlayer): def test_strategy(self): actions = [(C, C), (D, D), (C, C), (C, D), (D, C), (D, D)] - self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=2) + self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=50) + def test_strategy2(self): actions = [(C, C), (C, D), (D, C), (D, D), (C, C), (C, D)] - self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=31) + self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=1) + def test_strategy3(self): actions = [(C, D), (D, C), (D, D), (C, C), (C, D), (D, C)] self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=2) + def test_strategy4(self): actions = [(C, D), (C, C), (C, D), (D, C), (D, D), (C, C)] - self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=31) + self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=23) def test_four_vector(self): player = self.player() @@ -239,6 +247,7 @@ def test_strategy(self): actions = [(C, C), (C, D), (D, C), (C, D), (D, C), (C, D)] self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=2) + def test_strategy2(self): actions = [(C, D), (D, C), (C, D), (D, C), (C, D), (D, C)] self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=5) @@ -260,6 +269,8 @@ class TestALLCorALLD(TestPlayer): def test_strategy(self): actions = [(D, C)] * 10 self.versus_test(opponent=axl.Cooperator(), expected_actions=actions, seed=0) + + def test_strategy2(self): actions = [(C, C)] * 10 self.versus_test(opponent=axl.Cooperator(), expected_actions=actions, seed=1) From be374acab60cadd3601bd385f3a80b2c99516cb7 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 3 Jul 2020 20:25:53 -0700 Subject: [PATCH 031/121] Fix seeds for test_meta --- axelrod/tests/strategies/test_meta.py | 31 ++++++++++++++++----------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/axelrod/tests/strategies/test_meta.py b/axelrod/tests/strategies/test_meta.py index 87c79f3f4..bfb5a51c3 100644 --- a/axelrod/tests/strategies/test_meta.py +++ b/axelrod/tests/strategies/test_meta.py @@ -215,6 +215,8 @@ def test_strategy(self): expected_actions=actions, init_kwargs={"team": [axl.Cooperator, axl.Defector]}, ) + + def test_strategy2(self): actions = [(C, D)] + [(D, D)] * 7 self.versus_test( opponent=axl.Defector(), @@ -238,7 +240,7 @@ class TestMetaHunter(TestMetaPlayer): def test_strategy(self): # We are not using the Cooperator Hunter here, so this should lead to - # cooperation. + # cooperation. actions = [(C, C)] * 5 self.versus_test(opponent=axl.Cooperator(), expected_actions=actions) @@ -344,7 +346,7 @@ class TestMetaMajorityMemoryOne(TestMetaPlayer): def test_strategy(self): actions = [(C, C), (C, D), (D, C), (C, D), (D, C)] - self.versus_test(opponent=axl.Alternator(), expected_actions=actions) + self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=1) class TestMetaMajorityFiniteMemory(TestMetaPlayer): @@ -362,7 +364,7 @@ class TestMetaMajorityFiniteMemory(TestMetaPlayer): def test_strategy(self): actions = [(C, C), (C, D), (D, C), (C, D), (D, C)] - self.versus_test(opponent=axl.Alternator(), expected_actions=actions) + self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=2) class TestMetaMajorityLongMemory(TestMetaPlayer): @@ -382,6 +384,7 @@ def test_strategy(self): actions = [(C, C), (C, D), (D, C), (C, D), (D, C)] self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=0) + def test_strategy2(self): actions = [(C, C), (C, D), (D, C), (C, D), (D, C)] self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=1) @@ -401,7 +404,7 @@ class TestMetaWinnerMemoryOne(TestMetaPlayer): def test_strategy(self): actions = [(C, C), (C, D), (C, C), (D, D), (D, C)] - self.versus_test(opponent=axl.Alternator(), expected_actions=actions) + self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=1) class TestMetaWinnerFiniteMemory(TestMetaPlayer): @@ -419,7 +422,7 @@ class TestMetaWinnerFiniteMemory(TestMetaPlayer): def test_strategy(self): actions = [(C, C), (C, D), (C, C), (D, D), (D, C)] - self.versus_test(opponent=axl.Alternator(), expected_actions=actions) + self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=1) class TestMetaWinnerLongMemory(TestMetaPlayer): @@ -437,7 +440,7 @@ class TestMetaWinnerLongMemory(TestMetaPlayer): def test_strategy(self): actions = [(C, C), (C, D), (C, C), (D, D), (D, C)] - self.versus_test(opponent=axl.Alternator(), expected_actions=actions) + self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=4) class TestMetaWinnerDeterministic(TestMetaPlayer): @@ -473,7 +476,7 @@ class TestMetaWinnerStochastic(TestMetaPlayer): def test_strategy(self): actions = [(C, C), (C, D), (C, C), (D, D), (D, C)] - self.versus_test(opponent=axl.Alternator(), expected_actions=actions) + self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=1) class TestMetaMixer(TestMetaPlayer): @@ -545,7 +548,7 @@ def classifier_test(self, expected_class_classifier=None): def test_strategy(self): actions = [(C, C), (C, D), (D, C), (D, D), (D, C)] - self.versus_test(opponent=axl.Alternator(), expected_actions=actions) + self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=11) class TestNMWEStochastic(TestMetaPlayer): @@ -581,7 +584,7 @@ class TestNMWEFiniteMemory(TestMetaPlayer): def test_strategy(self): actions = [(C, C), (C, D), (D, C), (D, D), (D, C)] - self.versus_test(opponent=axl.Alternator(), expected_actions=actions) + self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=7) class TestNMWELongMemory(TestMetaPlayer): @@ -599,7 +602,7 @@ class TestNMWELongMemory(TestMetaPlayer): def test_strategy(self): actions = [(C, C), (C, D), (C, C), (D, D), (D, C)] - self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=10) + self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=3) class TestNMWEMemoryOne(TestMetaPlayer): @@ -617,7 +620,7 @@ class TestNMWEMemoryOne(TestMetaPlayer): def test_strategy(self): actions = [(C, C), (C, D), (C, C), (D, D), (D, C)] - self.versus_test(opponent=axl.Alternator(), expected_actions=actions) + self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=2) class TestMemoryDecay(TestPlayer): @@ -653,10 +656,12 @@ def test_strategy(self): actions = list(zip(mem_actions, opponent_actions)) self.versus_test(opponent, expected_actions=actions) + def test_strategy2(self): opponent = axl.Random() actions = [(C, D), (D, D), (D, C), (C, C), (C, D), (D, C)] - self.versus_test(opponent, expected_actions=actions, seed=0) + self.versus_test(opponent, expected_actions=actions, seed=15) + def test_strategy3(self): # Test net-cooperation-score (NCS) based decisions in subsequent turns opponent = axl.Cooperator() actions = [(C, C)] * 15 + [(C, C)] @@ -667,6 +672,7 @@ def test_strategy(self): init_kwargs={"memory": [D] * 5 + [C] * 10}, ) + def test_strategy4(self): opponent = axl.Cooperator() actions = [(C, C)] * 15 + [(C, C)] self.versus_test( @@ -676,6 +682,7 @@ def test_strategy(self): init_kwargs={"memory": [D] * 4 + [C] * 11}, ) + def test_alternative_starting_strategies(self): # Test alternative starting strategies opponent = axl.Cooperator() actions = list([(D, C)]) * 15 From 2cf50b6549c42deca30b4969775fbe0289b3a903 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 3 Jul 2020 20:40:25 -0700 Subject: [PATCH 032/121] Add exclusions for cloning test for Mind* strategies --- axelrod/tests/strategies/test_player.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/axelrod/tests/strategies/test_player.py b/axelrod/tests/strategies/test_player.py index a196f106e..9c5294420 100644 --- a/axelrod/tests/strategies/test_player.py +++ b/axelrod/tests/strategies/test_player.py @@ -461,7 +461,7 @@ def test_reset_clone(self): def test_clone_reproducible_play(self, seed, turns, noise): # Test that the cloned player produces identical play player = self.player() - if player.name in ["Darwin", "Human"]: + if player.name in ["Darwin", "Human", "Mind Bender", "Mind Controller", "Mind Warper"]: # Known exceptions return @@ -526,6 +526,7 @@ def versus_test( self, opponent, expected_actions, + turns=None, noise=None, seed=None, match_attributes=None, @@ -568,6 +569,7 @@ def versus_test( opponent, [x for (x, y) in expected_actions], [y for (x, y) in expected_actions], + turns=turns, noise=noise, seed=seed, attrs=attrs, @@ -623,6 +625,7 @@ def versus_test( player2, expected_actions1, expected_actions2, + turns=None, noise=None, seed=None, match_attributes=None, @@ -631,7 +634,8 @@ def versus_test( """Tests a sequence of outcomes for two given players.""" if len(expected_actions1) != len(expected_actions2): raise ValueError("Mismatched Expected History in TestMatch.") - turns = len(expected_actions1) + if not turns: + turns = len(expected_actions1) match = axl.Match( (player1, player2), turns=turns, noise=noise, seed=seed, From 13437ce5e66cc2e2e480af235e8e0286ea371ac4 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 3 Jul 2020 20:45:21 -0700 Subject: [PATCH 033/121] Update seeds for test_prober --- axelrod/tests/strategies/test_prober.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/axelrod/tests/strategies/test_prober.py b/axelrod/tests/strategies/test_prober.py index 771e0115a..229b3fe1e 100644 --- a/axelrod/tests/strategies/test_prober.py +++ b/axelrod/tests/strategies/test_prober.py @@ -283,20 +283,22 @@ def test_strategy(self): # Always retaliate a defection opponent = axl.MockPlayer([C, D, D, D, D]) actions = [(C, C), (C, D), (D, D), (D, D), (D, D)] - self.versus_test(opponent=opponent, expected_actions=actions) + self.versus_test(opponent=opponent, expected_actions=actions, seed=1) def test_random_defection(self): # Unprovoked defection with small probability actions = [(C, C), (D, C), (D, C), (C, C), (C, C)] self.versus_test( - opponent=axl.Cooperator(), expected_actions=actions, seed=2 + opponent=axl.Cooperator(), expected_actions=actions, seed=55 ) + def test_random_defection2(self): actions = [(C, C), (C, C), (C, C), (C, C), (D, C)] self.versus_test( - opponent=axl.Cooperator(), expected_actions=actions, seed=5 + opponent=axl.Cooperator(), expected_actions=actions, seed=31 ) + def test_random_defection3(self): # Always defect when p is 1 actions = [(C, C), (D, C), (D, C), (D, C), (D, C)] self.versus_test( @@ -343,18 +345,20 @@ def test_random_defection(self): self.versus_test( opponent=axl.Cooperator(), expected_actions=actions, - seed=2, + seed=55, attrs={"probing": True}, ) + def test_random_defection2(self): actions = [(C, C), (C, C), (C, C), (C, C), (D, C)] self.versus_test( opponent=axl.Cooperator(), expected_actions=actions, - seed=5, + seed=31, attrs={"probing": True}, ) + def test_random_defection3(self): # Always defect when p is 1 actions = [(C, C), (D, C), (D, C), (D, C), (D, C)] self.versus_test( @@ -371,7 +375,7 @@ def test_remorse(self): self.versus_test( opponent=opponent, expected_actions=actions, - seed=2, + seed=55, attrs={"probing": False}, ) From 9f2a9675929728aac53cc4c48d51e0d1468d68b8 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 3 Jul 2020 20:55:45 -0700 Subject: [PATCH 034/121] Update seeds for test_qlearner --- axelrod/tests/strategies/test_qlearner.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/axelrod/tests/strategies/test_qlearner.py b/axelrod/tests/strategies/test_qlearner.py index 2c6de5e17..948c8c02e 100644 --- a/axelrod/tests/strategies/test_qlearner.py +++ b/axelrod/tests/strategies/test_qlearner.py @@ -32,7 +32,7 @@ def test_strategy(self): self.versus_test( opponent=axl.Cooperator(), expected_actions=actions, - seed=5, + seed=32, attrs={ "Qs": { "": {C: 0, D: 0.9}, @@ -66,7 +66,7 @@ def test_strategy(self): self.versus_test( opponent=axl.Cooperator(), expected_actions=actions, - seed=5, + seed=32, attrs={ "Qs": { "": {C: 0, D: 0.9}, @@ -100,7 +100,7 @@ def test_strategy(self): self.versus_test( opponent=axl.Defector(), expected_actions=actions, - seed=5, + seed=32, attrs={ "Qs": { "": {C: 0, D: 0.1}, @@ -134,7 +134,7 @@ def test_strategy(self): self.versus_test( opponent=axl.Defector(), expected_actions=actions, - seed=5, + seed=32, attrs={ "Qs": { "": {C: 0, D: 0.1}, From 7983d93a5cb6a682dcdadcd84d60ade83e2a5cfa Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 3 Jul 2020 21:00:15 -0700 Subject: [PATCH 035/121] Update seeds for test_selfsteem --- axelrod/tests/strategies/test_selfsteem.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/axelrod/tests/strategies/test_selfsteem.py b/axelrod/tests/strategies/test_selfsteem.py index d6eca8c6e..d45730247 100644 --- a/axelrod/tests/strategies/test_selfsteem.py +++ b/axelrod/tests/strategies/test_selfsteem.py @@ -20,23 +20,25 @@ class TestSelfSteem(TestPlayer): "manipulates_state": False, } - def test_strategy(self): - + def test_strategy1(self): # Check for f > 0.95, defect actions = ( [(C, C), (C, C), (D, C), (D, C), (C, C), (D, C)] + [(C, C)] * 4 + [(D, C)] ) self.versus_test(axl.Cooperator(), expected_actions=actions, seed=1) + def test_strategy2(self): # Check for f < -0.95, cooperate actions = [(D, C), (C, C), (D, C), (D, C), (C, C), (D, C), (C, C), (C, C)] self.versus_test( - opponent=axl.Cooperator(), expected_actions=actions, seed=0 + opponent=axl.Cooperator(), expected_actions=actions, seed=10 ) + def test_strategy3(self): actions = [(D, D)] + [(D, D)] * 5 + [(D, D), (C, D), (C, D)] - self.versus_test(opponent=axl.Defector(), expected_actions=actions, seed=0) + self.versus_test(opponent=axl.Defector(), expected_actions=actions, seed=10) + def test_strategy4(self): # Check for -0.3 < f < 0.3, random actions = ( [(D, C), (C, C), (D, C), (D, C), (C, C), (D, C)] @@ -45,25 +47,28 @@ def test_strategy(self): + [(C, C)] * 7 ) self.versus_test( - opponent=axl.Cooperator(), expected_actions=actions, seed=6 + opponent=axl.Cooperator(), expected_actions=actions, seed=10 ) + def test_strategy5(self): actions = ( [(D, D)] * 7 + [(C, D), (C, D)] + [(D, D)] * 8 + [(C, D), (C, D), (D, D), (D, D), (D, D)] ) - self.versus_test(opponent=axl.Defector(), expected_actions=actions, seed=5) + self.versus_test(opponent=axl.Defector(), expected_actions=actions, seed=32) + def test_strategy6(self): # Check for 0.95 > abs(f) > 0.3, follows TitForTat actions = ( [(D, D)] * 5 + [(C, D), (D, D), (C, D), (C, D), (D, D), (C, D)] + [(D, D)] * 5 ) - self.versus_test(opponent=axl.Defector(), expected_actions=actions) + self.versus_test(opponent=axl.Defector(), expected_actions=actions, seed=17) + def test_strategy7(self): actions = [ (D, C), (C, C), @@ -76,4 +81,4 @@ def test_strategy(self): (C, C), (C, C), ] - self.versus_test(opponent=axl.Cooperator(), expected_actions=actions) + self.versus_test(opponent=axl.Cooperator(), expected_actions=actions, seed=10) From cec3538faa95cacf12a2a306602c7114ea59531f Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 3 Jul 2020 21:03:05 -0700 Subject: [PATCH 036/121] Update seeds for test_stalker --- axelrod/tests/strategies/test_stalker.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/axelrod/tests/strategies/test_stalker.py b/axelrod/tests/strategies/test_stalker.py index 37ab36a7f..59b6d1370 100644 --- a/axelrod/tests/strategies/test_stalker.py +++ b/axelrod/tests/strategies/test_stalker.py @@ -46,6 +46,7 @@ def test_strategy(self): opponent=axl.MockPlayer(actions=[C] * 7 + [D] * 5), expected_actions=actions ) + def test_strategy2(self): # current_average_score < 1 actions = ( [(C, D)] @@ -53,8 +54,9 @@ def test_strategy(self): + [(C, D)] * 3 + [(D, D), (C, D), (D, D), (C, D), (D, D), (C, D), (D, D)] ) - self.versus_test(axl.Defector(), expected_actions=actions, seed=6) + self.versus_test(axl.Defector(), expected_actions=actions, seed=3222) + def test_strategy3(self): actions = [(C, D)] * 3 + [ (D, D), (C, D), @@ -67,8 +69,9 @@ def test_strategy(self): (C, D), (D, D), ] - self.versus_test(axl.Defector(), expected_actions=actions, seed=7) + self.versus_test(axl.Defector(), expected_actions=actions, seed=649) + def test_strategy4(self): # defect in last round actions = [(C, C)] * 199 + [(D, C)] self.versus_test( @@ -83,11 +86,3 @@ def test_strategy(self): expected_actions=actions, match_attributes={"length": 4}, ) - - def test_reset(self): - player = axl.Stalker() - m = axl.Match((player, axl.Alternator()), seed=0) - m.play() - self.assertNotEqual(player.current_score, 0) - player.reset() - self.assertEqual(player.current_score, 0) From 2f6b43bf2a5d850ca660ecf2b7a657bb4d719ad5 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 3 Jul 2020 21:14:19 -0700 Subject: [PATCH 037/121] Update seeds for test_titfortat --- axelrod/tests/strategies/test_titfortat.py | 69 ++++++++++------------ 1 file changed, 30 insertions(+), 39 deletions(-) diff --git a/axelrod/tests/strategies/test_titfortat.py b/axelrod/tests/strategies/test_titfortat.py index 9601b5117..7a2e95948 100644 --- a/axelrod/tests/strategies/test_titfortat.py +++ b/axelrod/tests/strategies/test_titfortat.py @@ -50,7 +50,7 @@ def test_strategy(self): # We can also test against random strategies actions = [(C, D), (D, C), (C, C), (C, D), (D, D)] - self.versus_test(axl.Random(), expected_actions=actions, seed=0) + self.versus_test(axl.Random(), expected_actions=actions, seed=37) actions = [(C, C), (C, C), (C, C), (C, C)] self.versus_test(axl.Random(), expected_actions=actions, seed=1) @@ -722,43 +722,34 @@ def test_is_tit_for_tat_with_no_noise(self, strategies, turns): m2 = axl.Match((player, opponent), turns) self.assertEqual(m1.play(), m2.play()) - def test_strategy_with_noise(self): - player = self.player() - opponent = axl.Defector() - match = axl.Match((player, opponent), turns=1, seed=9) - match.play() - self.assertEqual(player.history[-1], C) - self.assertEqual(player._recorded_history, [C]) + def test_strategy_with_noise1(self): + self.versus_test(axl.Defector(), [(C, D)], turns=1, seed=9, + attrs={"_recorded_history": [C]}) - match = axl.Match((player, opponent), turns=1, noise=0.9, seed=9) - match.play() - self.assertEqual(player.history, [D]) - self.assertEqual(player._recorded_history, [C]) - self.assertEqual(opponent.history, [C]) + def test_strategy_with_noise2(self): + self.versus_test(axl.Defector(), [(D, C)], turns=1, noise=0.5, seed=11, + attrs={"_recorded_history": [C]}) + def test_strategy_with_noise3(self): # After noise: is contrite - match = axl.Match((player, opponent), turns=2, noise=0.9, seed=9) - match.play() - self.assertEqual(player.history, [D, C]) - self.assertEqual(player._recorded_history, [C, C]) - self.assertEqual(opponent.history, [C, D]) - self.assertTrue(player.contrite) + actions = list(zip([D, C], [C, D])) + self.versus_test(axl.Defector(), actions, turns=2, noise=0.5, seed=37, + attrs={"_recorded_history": [C, C], + "contrite": True}) + def test_strategy_with_noise4(self): # Cooperates and no longer contrite - match = axl.Match((player, opponent), turns=3, noise=0.9, seed=9) - match.play() - self.assertEqual(player.history, [D, C, C]) - self.assertEqual(player._recorded_history, [C, C, C]) - self.assertEqual(opponent.history, [C, D, D]) - self.assertFalse(player.contrite) - - # Goes back to playing tft - match = axl.Match((player, opponent), turns=4, noise=0.9, seed=9) - match.play() - self.assertEqual(player.history, [D, C, C, D]) - self.assertEqual(player._recorded_history, [C, C, C, D]) - self.assertEqual(opponent.history, [C, D, D, D]) - self.assertFalse(player.contrite) + actions = list(zip([D, C, C], [C, D, D])) + self.versus_test(axl.Defector(), actions, turns=3, noise=0.5, seed=151, + attrs={"_recorded_history": [C, C, C], + "contrite": False}) + + def test_strategy_with_noise5(self): + # Defects and no longer contrite + actions = list(zip([D, C, C, D], [C, D, D, D])) + self.versus_test(axl.Defector(), actions, turns=4, noise=0.5, seed=259, + attrs={"_recorded_history": [C, C, C, D], + "contrite": False}) class TestAdaptiveTitForTat(TestPlayer): @@ -979,7 +970,7 @@ def test_strategy(self): ) actions = [(C, D), (D, C), (C, C), (C, D), (D, D)] self.versus_test( - axl.Random(), expected_actions=actions, seed=0, init_kwargs=init_kwargs + axl.Random(), expected_actions=actions, seed=37, init_kwargs=init_kwargs ) actions = [(C, D), (D, C), (C, D), (D, D)] self.versus_test( @@ -1078,7 +1069,7 @@ def test_strategy(self): axl.Cooperator(), expected_actions=actions, attrs={"is_defector": False}, - seed=2, + seed=1, ) actions = [(C, C), (C, C), (C, C), (C, C)] @@ -1087,7 +1078,7 @@ def test_strategy(self): expected_actions=actions, attrs={"is_defector": False}, match_attributes={"length": float("inf")}, - seed=2, + seed=1, ) actions = [(C, D), (D, D), (D, D), (D, D)] @@ -1095,7 +1086,7 @@ def test_strategy(self): axl.Defector(), expected_actions=actions, attrs={"is_defector": False}, - seed=2, + seed=1, ) actions = [(C, D), (D, D), (D, D), (D, D)] @@ -1104,7 +1095,7 @@ def test_strategy(self): expected_actions=actions, attrs={"is_defector": False}, match_attributes={"length": float("inf")}, - seed=2, + seed=1, ) # Chance of becoming a defector is 50% after (D, C) occurs. @@ -1113,7 +1104,7 @@ def test_strategy(self): axl.Alternator(), expected_actions=actions, attrs={"is_defector": False}, - seed=3, + seed=1, ) actions = [(C, C), (C, D), (D, C), (D, D), (D, C), (D, D), (D, C)] From f124448a63a31074202d2140ff0b9a2c2afc5bcf Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 3 Jul 2020 21:17:11 -0700 Subject: [PATCH 038/121] Update seeds for test_worse_and_worse --- .../tests/strategies/test_worse_and_worse.py | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/axelrod/tests/strategies/test_worse_and_worse.py b/axelrod/tests/strategies/test_worse_and_worse.py index a402a5e15..f5ab0943c 100644 --- a/axelrod/tests/strategies/test_worse_and_worse.py +++ b/axelrod/tests/strategies/test_worse_and_worse.py @@ -25,14 +25,15 @@ def test_strategy(self): """Test that the strategy gives expected behaviour.""" # 6 Rounds Cooperate given seed actions = [(C, C)] * 6 + [(D, C)] + [(C, C)] * 3 - self.versus_test(axl.Cooperator(), expected_actions=actions, seed=8) + self.versus_test(axl.Cooperator(), expected_actions=actions, seed=144) - # 6 Rounds Cooperate and Defect no matter oponent + def test_strategy2(self): + # 6 Rounds Cooperate and Defect no matter opponent actions = [(C, D)] * 6 + [(D, D)] + [(C, D)] * 3 - self.versus_test(axl.Defector(), expected_actions=actions, seed=8) + self.versus_test(axl.Defector(), expected_actions=actions, seed=144) -class TestWorseAndWorseRandom(TestPlayer): +class TestKnowledgeableWorseAndWorse(TestPlayer): name = "Knowledgeable Worse and Worse" player = axl.KnowledgeableWorseAndWorse @@ -56,6 +57,7 @@ def test_strategy(self): seed=1, ) + def test_strategy2(self): # Test that behaviour does not depend on opponent actions = [(C, D)] + [(D, D)] * 4 self.versus_test( @@ -65,6 +67,7 @@ def test_strategy(self): seed=1, ) + def test_strategy3(self): # Test that behaviour changes when does not know length. actions = [(C, C), (C, D), (C, C), (C, D), (C, C)] self.versus_test( @@ -96,30 +99,34 @@ def test_strategy(self): actions = [(C, C)] * 19 self.versus_test(axl.Cooperator(), expected_actions=actions) + def test_strategy2(self): actions = [(C, C), (C, C), (C, D), (D, C)] self.versus_test( opponent=axl.MockPlayer(actions=[C, C, D, C]), expected_actions=actions ) + def test_strategy3(self): actions = [(C, C)] * 18 + [(C, D), (D, C)] self.versus_test( opponent=axl.MockPlayer(actions=[C] * 18 + [D, C]), expected_actions=actions, ) + def test_strategy4(self): # After round 20, strategy follows stochastic behavior given a seed actions = [(C, C)] * 20 + [(C, D), (D, C), (C, C), (C, D)] self.versus_test( opponent=axl.MockPlayer(actions=[C] * 20 + [D, C, C, D]), expected_actions=actions, - seed=8, + seed=20, ) + def test_strategy5(self): actions = [(C, C)] * 20 + [(D, D), (D, C)] + [(C, C)] * 2 + [(D, C)] self.versus_test( opponent=axl.MockPlayer(actions=[C] * 20 + [D, C, C, C]), expected_actions=actions, - seed=2, + seed=338, ) @@ -143,15 +150,17 @@ def test_strategy(self): actions = [(C, D)] + [(D, D)] * 4 self.versus_test(axl.Defector(), expected_actions=actions) + def test_strategy2(self): # Test that if opponent only cooperates, strategy also cooperates actions = [(C, C)] * 5 self.versus_test(axl.Cooperator(), expected_actions=actions) + def test_strategy3(self): # Test that given a non 0/1 probability of defecting, strategy follows # stochastic behaviour, given a seed actions = [(C, C), (C, D), (C, C), (D, D), (C, C), (D, C)] self.versus_test( axl.MockPlayer(actions=[C, D, C, D, C]), expected_actions=actions, - seed=8, + seed=18, ) From 4d5fee01bef6b711901d52502192c0016747ae41 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 3 Jul 2020 21:23:34 -0700 Subject: [PATCH 039/121] Update seeds for test_zero_determinant --- .../tests/strategies/test_zero_determinant.py | 66 +++++++++++-------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/axelrod/tests/strategies/test_zero_determinant.py b/axelrod/tests/strategies/test_zero_determinant.py index 615ca27fd..75b98fb5f 100644 --- a/axelrod/tests/strategies/test_zero_determinant.py +++ b/axelrod/tests/strategies/test_zero_determinant.py @@ -38,11 +38,12 @@ def test_four_vector(self): def test_strategy(self): actions = [(C, C), (C, D), (D, C), (D, D), (D, C), (D, D)] self.versus_test( - opponent=axl.Alternator(), expected_actions=actions, seed=3 + opponent=axl.Alternator(), expected_actions=actions, seed=1 ) + def test_strategy2(self): actions = [(C, D), (D, C), (D, D), (D, C), (D, D), (D, C)] - self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=6) + self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=1) class TestZDExtort2(TestPlayer): @@ -71,19 +72,22 @@ def test_receive_match_attributes(self): def test_strategy(self): actions = [(C, C), (D, D), (D, C), (D, D), (D, C), (C, D)] self.versus_test( - opponent=axl.Alternator(), expected_actions=actions, seed=2 + opponent=axl.Alternator(), expected_actions=actions, seed=57 ) + def test_strategy2(self): actions = [(C, C), (C, D), (C, C), (C, D), (D, C), (C, D)] self.versus_test( - opponent=axl.Alternator(), expected_actions=actions, seed=31 + opponent=axl.Alternator(), expected_actions=actions, seed=44 ) + def test_strategy3(self): actions = [(C, D), (D, C), (D, D), (D, C), (C, D), (C, C)] - self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=2) + self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=10) + def test_strategy4(self): actions = [(C, D), (C, C), (C, D), (C, C), (C, D), (C, C)] - self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=31) + self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=7) class TestZDExtort2v2(TestPlayer): @@ -112,11 +116,12 @@ def test_four_vector(self): def test_strategy(self): actions = [(C, C), (D, D), (D, C), (D, D), (D, C), (C, D)] self.versus_test( - opponent=axl.Alternator(), expected_actions=actions, seed=2 + opponent=axl.Alternator(), expected_actions=actions, seed=57 ) + def test_strategy2(self): actions = [(C, D), (D, C), (D, D), (D, C), (D, D), (D, C)] - self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=5) + self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=2) class TestZDExtort3(TestPlayer): @@ -143,14 +148,13 @@ def test_four_vector(self): def test_strategy(self): actions = [(C, C), (C, D), (D, C), (D, D), (D, C), (D, D)] - self.versus_test( - opponent=axl.Alternator(), expected_actions=actions, seed=3 + opponent=axl.Alternator(), expected_actions=actions, seed=1 ) + def test_strategy2(self): actions = [(C, D), (D, C), (D, D), (D, C), (D, D), (D, C)] - - self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=6) + self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=2) class TestZDExtort4(TestPlayer): @@ -174,11 +178,12 @@ def test_four_vector(self): def test_strategy(self): actions = [(C, C), (D, D), (D, C), (D, D), (D, C), (C, D)] self.versus_test( - opponent=axl.Alternator(), expected_actions=actions, seed=2 + opponent=axl.Alternator(), expected_actions=actions, seed=10 ) + def test_strategy2(self): actions = [(C, D), (D, C), (D, D), (D, C), (D, D), (D, C)] - self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=5) + self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=10) class TestZDGen2(TestPlayer): @@ -200,22 +205,24 @@ def test_four_vector(self): test_four_vector(self, expected_dictionary) def test_strategy(self): - actions = [(C, C), (C, D), (D, C), (D, D), (C, C), (C, D)] self.versus_test( - opponent=axl.Alternator(), expected_actions=actions, seed=2 + opponent=axl.Alternator(), expected_actions=actions, seed=10 ) + def test_strategy2(self): actions = [(C, C), (C, D), (C, C), (C, D), (C, C), (C, D)] self.versus_test( - opponent=axl.Alternator(), expected_actions=actions, seed=31 + opponent=axl.Alternator(), expected_actions=actions, seed=2 ) + def test_strategy3(self): actions = [(C, D), (D, C), (D, D), (C, C), (C, D), (C, C)] - self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=2) + self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=10) + def test_strategy4(self): actions = [(C, D), (C, C), (C, D), (C, C), (C, D), (C, C)] - self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=31) + self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=3) class TestZDGTFT2(TestPlayer): @@ -243,19 +250,22 @@ def test_receive_match_attributes(self): def test_strategy(self): actions = [(C, C), (C, D), (D, C), (C, D), (D, C), (C, D)] self.versus_test( - opponent=axl.Alternator(), expected_actions=actions, seed=2 + opponent=axl.Alternator(), expected_actions=actions, seed=1 ) + def test_strategy2(self): actions = [(C, C), (C, D), (C, C), (C, D), (C, C), (C, D)] self.versus_test( - opponent=axl.Alternator(), expected_actions=actions, seed=31 + opponent=axl.Alternator(), expected_actions=actions, seed=23 ) + def test_strategy3(self): actions = [(C, D), (D, C), (C, D), (D, C), (C, D), (C, C)] - self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=2) + self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=4) + def test_strategy4(self): actions = [(C, D), (C, C), (C, D), (C, C), (C, D), (D, C)] - self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=31) + self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=23) class TestZDMischief(TestPlayer): @@ -279,11 +289,12 @@ def test_four_vector(self): def test_strategy(self): actions = [(C, C), (D, D), (D, C), (D, D), (D, C), (C, D)] self.versus_test( - opponent=axl.Alternator(), expected_actions=actions, seed=2 + opponent=axl.Alternator(), expected_actions=actions, seed=10 ) + def test_strategy2(self): actions = [(C, D), (D, C), (D, D), (D, C), (D, D), (D, C)] - self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=5) + self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=4) class TestZDSet2(TestPlayer): @@ -312,8 +323,9 @@ def test_four_vector(self): def test_strategy(self): actions = [(C, C), (D, D), (D, C), (C, D), (C, C), (D, D)] self.versus_test( - opponent=axl.Alternator(), expected_actions=actions, seed=2 + opponent=axl.Alternator(), expected_actions=actions, seed=151 ) + def test_strategy2(self): actions = [(C, D), (D, C), (D, D), (D, C), (D, D), (D, C)] - self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=5) + self.versus_test(opponent=axl.CyclerDC(), expected_actions=actions, seed=12) From 5d1cff0dcd725f490328c7725045eafae67877dc Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 3 Jul 2020 21:42:11 -0700 Subject: [PATCH 040/121] Rework test_calculator --- axelrod/tests/strategies/test_calculator.py | 93 +++++++-------------- 1 file changed, 28 insertions(+), 65 deletions(-) diff --git a/axelrod/tests/strategies/test_calculator.py b/axelrod/tests/strategies/test_calculator.py index 3b2dc66c8..199bf69da 100644 --- a/axelrod/tests/strategies/test_calculator.py +++ b/axelrod/tests/strategies/test_calculator.py @@ -22,81 +22,45 @@ class TestCalculator(TestPlayer): "manipulates_state": False, } - def test_twenty_rounds_joss_then_defects_for_cyclers(self): + def test_twenty_rounds_joss_for_cyclers(self): """Uses axelrod.strategies.axelrod_first.Joss strategy for first 20 rounds""" - seed = 2 - flip_indices = [1, 3] - twenty_alternator_actions = [C, D] * 10 - twenty_test_actions = get_joss_strategy_actions( - twenty_alternator_actions, flip_indices - ) - - expected_actions = twenty_test_actions + [(D, C), (D, D), (D, C), (D, D)] + seed = 4 + match = axl.Match((axl.FirstByJoss(), axl.Alternator()), turns=20, seed=seed) + match.play() self.versus_test( - axl.Alternator(), expected_actions=twenty_test_actions, seed=seed - ) - self.versus_test( - axl.Alternator(), expected_actions=expected_actions, seed=seed + axl.Alternator(), expected_actions=match.result, seed=seed ) - def test_twenty_rounds_joss_then_tit_for_tat_for_non_cyclers(self): + def test_twenty_rounds_joss_then_defects_for_cyclers(self): """Uses axelrod.strategies.axelrod_first.Joss strategy for first 20 rounds""" - seed = 2 - flip_indices = [1, 2] - - twenty_non_cyclical_actions = [ - C, - C, - D, - C, - C, - D, - C, - C, - C, - D, - C, - C, - C, - C, - D, - C, - C, - C, - C, - C, - ] - twenty_test_actions = get_joss_strategy_actions( - twenty_non_cyclical_actions, flip_indices + seed = 4 + match = axl.Match((axl.FirstByJoss(), axl.Alternator()), turns=20, seed=seed) + match.play() + expected_actions = match.result + [(D, C), (D, D), (D, C), (D, D)] + self.versus_test( + axl.Alternator(), expected_actions=expected_actions, seed=seed, turns=24, ) - subsequent_opponent_actions = [D, C, D, C, D, C, D, C] - subsequent_test_actions = [ - (C, D), - (D, C), - (C, D), - (D, C), - (C, D), - (D, C), - (C, D), - (D, C), - ] - - opponent_actions = twenty_non_cyclical_actions + subsequent_opponent_actions - test_actions = twenty_test_actions + subsequent_test_actions + def test_twenty_rounds_joss_for_noncyclers(self): + """Uses axelrod.strategies.axelrod_first.Joss strategy for first 20 rounds""" + seed = 4 + match = axl.Match((axl.FirstByJoss(), axl.AntiCycler()), turns=20, seed=seed) + match.play() self.versus_test( - axl.MockPlayer(actions=twenty_non_cyclical_actions), - expected_actions=twenty_test_actions, - seed=seed, + axl.AntiCycler(), expected_actions=match.result, seed=seed ) + + def test_twenty_rounds_joss_then_tft_for_noncyclers(self): + """Uses axelrod.strategies.axelrod_first.Joss strategy for first 20 rounds""" + seed = 4 + match = axl.Match((axl.FirstByJoss(), axl.AntiCycler()), turns=20, seed=seed) + match.play() + expected_actions = match.result + [(C, C), (C, C), (C, D), (D, C), (C, C)] self.versus_test( - axl.MockPlayer(actions=opponent_actions), - expected_actions=test_actions, - seed=seed, + axl.AntiCycler(), expected_actions=expected_actions, seed=seed, turns=24, ) def test_edge_case_calculator_sees_cycles_of_size_ten(self): - seed = 3 ten_length_cycle = [C, D, C, C, D, C, C, C, D, C] self.assertEqual(detect_cycle((ten_length_cycle * 2)), tuple(ten_length_cycle)) @@ -108,11 +72,10 @@ def test_edge_case_calculator_sees_cycles_of_size_ten(self): self.versus_test( axl.MockPlayer(actions=opponent_actions), expected_actions=expected, - seed=seed, + seed=14, ) def test_edge_case_calculator_ignores_cycles_gt_len_ten(self): - seed = 3 eleven_length_cycle = [D, D, C, C, D, C, C, C, D, C, D] twenty_rounds_of_eleven_len_cycle = ( eleven_length_cycle + eleven_length_cycle[:9] @@ -128,7 +91,7 @@ def test_edge_case_calculator_ignores_cycles_gt_len_ten(self): self.versus_test( axl.MockPlayer(actions=opponent_actions), expected_actions=uses_tit_for_tat_after_twenty_rounds, - seed=seed, + seed=3, ) def test_get_joss_strategy_actions(self): From d9d95a0a18b7907a89035831def2585a527b90c7 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sat, 4 Jul 2020 09:26:35 -0700 Subject: [PATCH 041/121] Update seeds for several tests in axelrod_second --- .../tests/strategies/test_axelrod_second.py | 134 ++++++++++++------ 1 file changed, 89 insertions(+), 45 deletions(-) diff --git a/axelrod/tests/strategies/test_axelrod_second.py b/axelrod/tests/strategies/test_axelrod_second.py index 760317b24..b20f0a614 100644 --- a/axelrod/tests/strategies/test_axelrod_second.py +++ b/axelrod/tests/strategies/test_axelrod_second.py @@ -20,25 +20,20 @@ class TestChampion(TestPlayer): "manipulates_state": False, } - def test_strategy(self): + def test_initial_rounds(self): # Cooperates for first 10 rounds + actions = [(C, D)] * 10 # Cooperate for ten rounds + self.versus_test(axl.Defector(), expected_actions=actions) + # Mirror partner for next phase + actions += [(D, D)] * 7 # Mirror opponent afterwards + self.versus_test(axl.Defector(), expected_actions=actions) + def test_cooperate_until_defect(self): actions = [(C, C), (C, D)] * 5 # Cooperate for ten rounds - self.versus_test(axl.Alternator(), expected_actions=actions) - - # Mirror partner for next phase actions += [(D, C), (C, D)] * 7 # Mirror opponent afterwards - self.versus_test(axl.Alternator(), expected_actions=actions) - # Cooperate unless the opponent defected, has defected at least 40% of - actions_1 = actions + [(D, C), (C, D), (C, C), (C, D)] - self.versus_test(axl.Alternator(), expected_actions=actions_1, seed=0) - - actions_2 = actions + [(D, C), (C, D), (D, C), (C, D)] - self.versus_test(axl.Alternator(), expected_actions=actions_2, seed=1) - - actions_3 = actions + [(D, C), (C, D), (C, C), (C, D)] - self.versus_test(axl.Alternator(), expected_actions=actions_3, seed=2) + actions += [(D, C), (C, D), (C, C), (C, D)] * 3 + self.versus_test(axl.Alternator(), expected_actions=actions, seed=2) class TestEatherley(TestPlayer): @@ -64,15 +59,21 @@ def test_strategy(self): actions = [(C, D), (D, D), (D, D), (D, D), (D, D)] self.versus_test(axl.Defector(), expected_actions=actions) + def test_stocastic_response1(self): # Stochastic response to defect actions = [(C, C), (C, D), (D, C), (C, D), (D, C)] - self.versus_test(axl.Alternator(), expected_actions=actions, seed=0) + self.versus_test(axl.Alternator(), expected_actions=actions, seed=10) + + def test_stocastic_response2(self): actions = [(C, C), (C, D), (C, C), (C, D), (D, C)] self.versus_test(axl.Alternator(), expected_actions=actions, seed=1) + def test_stocastic_response3(self): opponent = axl.MockPlayer(actions=[D, C, C, D]) actions = [(C, D), (D, C), (C, C), (C, D), (C, D)] - self.versus_test(opponent, expected_actions=actions, seed=8) + self.versus_test(opponent, expected_actions=actions, seed=1) + + def test_stocastic_response4(self): opponent = axl.MockPlayer(actions=[D, C, C, D]) actions = [(C, D), (D, C), (C, C), (C, D), (D, D)] self.versus_test(opponent, expected_actions=actions, seed=2) @@ -168,12 +169,9 @@ class TestTranquilizer(TestPlayer): "manipulates_state": False, } - # test for initalised variables - def test_init(self): - + # test for initalised variables player = axl.SecondByTranquilizer() - self.assertEqual(player.num_turns_after_good_defection, 0) self.assertEqual(player.opponent_consecutive_defections, 0) self.assertEqual(player.one_turn_after_good_defection_ratio, 5) @@ -182,7 +180,6 @@ def test_init(self): self.assertEqual(player.two_turns_after_good_defection_ratio_count, 1) def test_strategy(self): - opponent = axl.Bully() actions = [(C, D), (D, D), (D, C), (C, C), (C, D), (D, D), (D, C), (C, C)] expected_attrs = { @@ -218,8 +215,8 @@ def test_strategy(self): } self.versus_test(opponent, expected_actions=actions, attrs=expected_attrs) + def test_strategy2(self): # If score is between 1.75 and 2.25, may cooperate or defect - opponent = axl.MockPlayer(actions=[D] * 3 + [C] * 4 + [D] * 2) actions = [(C, D)] + [(D, D)] * 2 + [(D, C)] + [(C, C)] * 3 + [(C, D)] actions += [(C, D)] # <-- Random @@ -231,9 +228,10 @@ def test_strategy(self): "two_turns_after_good_defection_ratio_count": 1, } self.versus_test( - opponent, expected_actions=actions, seed=0, attrs=expected_attrs + opponent, expected_actions=actions, seed=1, attrs=expected_attrs ) + def test_strategy3(self): opponent = axl.MockPlayer(actions=[D] * 3 + [C] * 4 + [D] * 2) actions = [(C, D)] + [(D, D)] * 2 + [(D, C)] + [(C, C)] * 3 + [(C, D)] actions += [(D, D)] # <-- Random @@ -245,9 +243,10 @@ def test_strategy(self): "two_turns_after_good_defection_ratio_count": 1, } self.versus_test( - opponent, expected_actions=actions, seed=17, attrs=expected_attrs + opponent, expected_actions=actions, seed=8, attrs=expected_attrs ) + def test_strategy4(self): """If score is greater than 2.25 either cooperate or defect, if turn number <= 5; cooperate""" @@ -264,6 +263,7 @@ def test_strategy(self): opponent, expected_actions=actions, seed=1, attrs=expected_attrs ) + def test_strategy5(self): opponent = axl.MockPlayer(actions=[C] * 5) actions = [(C, C)] * 4 + [(D, C)] expected_attrs = { @@ -274,9 +274,10 @@ def test_strategy(self): "two_turns_after_good_defection_ratio_count": 1, } self.versus_test( - opponent, expected_actions=actions, seed=89, attrs=expected_attrs + opponent, expected_actions=actions, seed=45, attrs=expected_attrs ) + def test_strategy6(self): """ Given score per turn is greater than 2.25, Tranquilizer will never defect twice in a row""" @@ -290,9 +291,10 @@ def test_strategy(self): "two_turns_after_good_defection_ratio_count": 1, } self.versus_test( - opponent, expected_actions=actions, seed=89, attrs=expected_attrs + opponent, expected_actions=actions, seed=45, attrs=expected_attrs ) + def test_strategy7(self): # Tests cooperation after update_state opponent = axl.MockPlayer(actions=[C] * 5) @@ -305,9 +307,10 @@ def test_strategy(self): "two_turns_after_good_defection_ratio_count": 1, } self.versus_test( - opponent, expected_actions=actions, seed=89, attrs=expected_attrs + opponent, expected_actions=actions, seed=45, attrs=expected_attrs ) + def test_strategy8(self): # Ensures FD1 values are calculated opponent = axl.MockPlayer(actions=[C] * 6) @@ -320,9 +323,10 @@ def test_strategy(self): "two_turns_after_good_defection_ratio_count": 1, } self.versus_test( - opponent, expected_actions=actions, seed=89, attrs=expected_attrs + opponent, expected_actions=actions, seed=45, attrs=expected_attrs ) + def test_strategy9(self): # Ensures FD2 values are calculated opponent = axl.MockPlayer(actions=[C] * 6) @@ -335,11 +339,11 @@ def test_strategy(self): "two_turns_after_good_defection_ratio_count": 2, } self.versus_test( - opponent, expected_actions=actions, seed=89, attrs=expected_attrs + opponent, expected_actions=actions, seed=45, attrs=expected_attrs ) + def test_strategy10(self): # Ensures scores are being counted - opponent = axl.Defector() actions = [(C, D)] + [(D, D)] * 19 expected_attrs = { @@ -557,14 +561,17 @@ def test_strategy(self): (C, C), (C, D), ] - self.versus_test(axl.Alternator(), expected_actions=actions, seed=1) + self.versus_test(axl.Alternator(), expected_actions=actions, seed=36) + def test_strategy2(self): actions = [(C, C), (C, D), (C, C), (D, D), (D, C), (C, D)] - self.versus_test(axl.Alternator(), expected_actions=actions, seed=2) + self.versus_test(axl.Alternator(), expected_actions=actions, seed=10) + def test_strategy3(self): actions = [(C, C), (C, D), (D, C), (C, D), (D, C), (C, D), (C, C)] self.versus_test(axl.Alternator(), expected_actions=actions, seed=3) + def test_strategy4(self): # Now we have to test the detect-random logic, which doesn't pick up # until after 26 turns. So we need a big sample. actions = [ @@ -690,7 +697,7 @@ def test_strategy(self): actions = [(C, C)] * 100 self.versus_test(axl.Cooperator(), expected_actions=actions) - # It will take until turn 18 to respond decide to repond D->D + # It will take until turn 18 to respond decide to respond D->D actions = [(C, D)] actions += [ (C, D), @@ -714,6 +721,7 @@ def test_strategy(self): actions += [(D, D)] * 30 # Defect self.versus_test(axl.Defector(), expected_actions=actions, seed=1) + def test_strategy2(self): # Highly-defective opponent # It will take until turn 20 to respond decide to repond D to C opponent_actions = [D] * 17 + [C, C, C, C] @@ -743,6 +751,7 @@ def test_strategy(self): actions += [(D, C), (D, C)] self.versus_test(almost_defector, expected_actions=actions, seed=1) + def test_strategy3(self): # Here it will take until turn 40 to detect random and defect actions = [(C, C)] actions += [ @@ -788,7 +797,7 @@ def test_strategy(self): (D, C), ] # 17 D have come, so tit for tat for a while actions += [(D, D), (D, C)] * 100 # Random finally detected - self.versus_test(axl.Alternator(), expected_actions=actions, seed=2) + self.versus_test(axl.Alternator(), expected_actions=actions, seed=114940) class TestWmAdams(TestPlayer): @@ -833,6 +842,8 @@ def test_strategy(self): (D, D), ] self.versus_test(axl.Defector(), expected_actions=actions, seed=1) + + def test_strategy2(self): actions = [ (C, D), (C, D), @@ -851,8 +862,9 @@ def test_strategy(self): (D, D), (D, D), ] - self.versus_test(axl.Defector(), expected_actions=actions, seed=2) + self.versus_test(axl.Defector(), expected_actions=actions, seed=10) + def test_strategy3(self): # After responding to the 11th D (counted as 10 D), just start cooperating opponent_actions = [D] * 11 + [C] * 100 changed_man = axl.MockPlayer(actions=opponent_actions) @@ -1004,6 +1016,7 @@ def test_strategy(self): Defect37, expected_actions=actions, attrs={"mode": "Fair-weather"} ) + def test_strategy2(self): # Defect on 37th turn to activate Fair-weather, then later defect to # exit Fair-weather opponent_actions = [C] * 36 + [D] + [C] * 100 + [D] + [C] * 4 @@ -1013,16 +1026,21 @@ def test_strategy(self): # Immediately exit Fair-weather actions += [(D, C), (C, C), (D, C), (C, C)] self.versus_test( - Defect37_big, expected_actions=actions, seed=2, attrs={"mode": "Normal"} + Defect37_big, expected_actions=actions, seed=10, attrs={"mode": "Normal"} ) + + def test_strategy3(self): actions = [(C, C)] * 36 + [(D, D)] + [(C, C)] * 100 actions += [(C, D)] # Immediately exit Fair-weather actions += [(D, C), (C, C), (C, C), (C, C)] + opponent_actions = [C] * 36 + [D] + [C] * 100 + [D] + [C] * 4 + Defect37_big = axl.MockPlayer(actions=opponent_actions) self.versus_test( Defect37_big, expected_actions=actions, seed=1, attrs={"mode": "Normal"} ) + def test_strategy4(self): # Opponent defects on 1st turn opponent_actions = [D] + [C] * 46 Defect1 = axl.MockPlayer(actions=opponent_actions) @@ -1035,8 +1053,9 @@ def test_strategy(self): actions += [(D, C), (C, C), (C, C)] # Don't draw next random number until now. Again DCC. actions += [(D, C), (C, C), (C, C)] - self.versus_test(Defect1, expected_actions=actions, seed=2) + self.versus_test(Defect1, expected_actions=actions, seed=19) + def test_strategy5(self): # Defection on turn 37 by opponent doesn't have an effect here opponent_actions = [D] + [C] * 35 + [D] + [C] * 10 Defect1_37 = axl.MockPlayer(actions=opponent_actions) @@ -1045,8 +1064,9 @@ def test_strategy(self): actions += [(C, C)] * 2 actions += [(D, C), (C, C), (C, C)] actions += [(D, C), (C, C), (C, C)] - self.versus_test(Defect1_37, expected_actions=actions, seed=2) + self.versus_test(Defect1_37, expected_actions=actions, seed=19) + def test_strategy6(self): # However a defect on turn 38 would be considered a burn. opponent_actions = [D] + [C] * 36 + [D] + [C] * 9 Defect1_38 = axl.MockPlayer(actions=opponent_actions) @@ -1060,6 +1080,7 @@ def test_strategy(self): Defect1_38, expected_actions=actions, seed=2, attrs={"burned": True} ) + def test_strategy7(self): # Use alternator to test parity flags. actions = [(C, C), (C, D)] # Even streak is set to 2, one for the opponent's defect and one for @@ -1079,7 +1100,7 @@ def test_strategy(self): # Repeat. Notice that the last turn is the 37th move, but we do not # defect. actions += [(C, D), (D, C), (C, D), (D, C), (C, D), (C, C)] - self.versus_test(axl.Alternator(), expected_actions=actions) + self.versus_test(axl.Alternator(), expected_actions=actions, seed=10) # Test for parity limit shortening. opponent_actions = [D, C] * 1000 @@ -1117,6 +1138,7 @@ def test_strategy(self): attrs={"recorded_defects": 119}, ) + def test_strategy8(self): # Detect random expected_actions = [ (C, D), @@ -1151,6 +1173,10 @@ def test_strategy(self): ] # Enter defect mode. expected_actions += [(D, C)] + + self.versus_test(axl.Random(), expected_actions=expected_actions, + turns=len(expected_actions), seed=10) + player = self.player() match = axl.Match((player, axl.Random()), turns=len(expected_actions), seed=10) # The history matrix will be [[0, 2], [5, 6], [3, 6], [4, 2]] @@ -1160,6 +1186,7 @@ def test_strategy(self): player.calculate_chi_squared(len(expected_actions)), 2.395, places=3 ) + def test_strategy9(self): # Come back out of defect mode opponent_actions = [ D, @@ -1351,6 +1378,7 @@ def test_strategy(self): actions = [(C, C)] * 100 self.versus_test(axl.Cooperator(), expected_actions=actions) + def test_strategy2(self): actions = [(C, D), (C, D), (D, D), (D, D), (D, D)] self.versus_test( axl.Defector(), @@ -1359,11 +1387,12 @@ def test_strategy(self): attrs={"flack": 15.0 / 16.0}, ) + def test_strategy3(self): actions = [(C, C), (C, D), (C, C), (C, D), (D, C), (C, D)] self.versus_test( axl.Alternator(), expected_actions=actions, - seed=4, + seed=1, attrs={"flack": 5.0 / 16.0}, ) @@ -1385,11 +1414,15 @@ def test_strategy(self): actions = [(C, C)] * 100 self.versus_test(axl.Cooperator(), expected_actions=actions) + def test_strategy2(self): actions = [(C, D), (C, D), (D, D), (D, D)] self.versus_test(axl.Defector(), expected_actions=actions, seed=1) + + def test_strategy3(self): actions = [(C, D), (D, D), (D, D), (C, D)] - self.versus_test(axl.Defector(), expected_actions=actions, seed=2) + self.versus_test(axl.Defector(), expected_actions=actions, seed=10) + def test_strategy4(self): actions = [ (C, D), (C, C), @@ -1405,8 +1438,11 @@ def test_strategy(self): axl.SuspiciousTitForTat(), expected_actions=actions, seed=1 ) + def test_strategy5(self): actions = [(C, C), (C, D), (D, C)] + [(D, D), (C, C)] * 3 self.versus_test(axl.Alternator(), expected_actions=actions, seed=2) + + def test_strategy6(self): actions = [(C, C), (C, D), (C, C)] + [(D, D), (C, C)] * 3 self.versus_test(axl.Alternator(), expected_actions=actions, seed=3) @@ -1431,6 +1467,7 @@ def test_strategy(self): actions = [(C, D)] * 10 + [(D, D)] * 20 self.versus_test(axl.Defector(), expected_actions=actions) + def test_strategy2(self): actions = [ (C, D), (C, D), @@ -1454,6 +1491,8 @@ def test_strategy(self): (D, C), ] self.versus_test(axl.Random(0.5), expected_actions=actions, seed=6) + + def test_strategy3(self): actions = [ (C, C), (C, D), @@ -1496,6 +1535,7 @@ def test_strategy(self): actions = [(C, C)] * 30 self.versus_test(axl.Cooperator(), expected_actions=actions) + def test_strategy2(self): actions = [(C, D)] * 5 actions += [ (D, D), @@ -1509,8 +1549,9 @@ def test_strategy(self): (D, D), (C, D), ] - self.versus_test(axl.Defector(), expected_actions=actions, seed=1) + self.versus_test(axl.Defector(), expected_actions=actions, seed=87) + def test_strategy3(self): actions = [(C, D)] * 5 actions += [ (D, D), @@ -1524,7 +1565,7 @@ def test_strategy(self): (D, D), (D, D), ] - self.versus_test(axl.Defector(), expected_actions=actions, seed=15) + self.versus_test(axl.Defector(), expected_actions=actions, seed=1581) class TestRichardHufford(TestPlayer): @@ -1923,6 +1964,7 @@ def test_strategy(self): actions = [(C, C)] * 100 self.versus_test(axl.Cooperator(), expected_actions=actions) + def test_strategy2(self): opponent = axl.Defector() # Cooperate always the first 4 turns actions = [(C, D)] * 4 @@ -1965,6 +2007,7 @@ def test_strategy(self): self.versus_test(opponent, expected_actions=actions, seed=1, attrs={"first_opp_def": True}) + def test_strategy3(self): # An opponent who defects for a long time, then tries cooperating opponent_actions = [C] * 30 + [D] + [C] * 10 MostlyCooperates = axl.MockPlayer(actions=opponent_actions) @@ -1988,12 +2031,13 @@ def test_strategy(self): self.versus_test(opponent, expected_actions=actions, attrs={"opp_c_after_x": {C: 2, D: 1}, "total_num_of_x": {C: 4, D: 1}, - "first_opp_def": False}, seed=100) + "first_opp_def": False}, seed=1) # Always cooperate, because we forgive the first defect actions += [(C, C)] self.versus_test(opponent, expected_actions=actions, - attrs={"first_opp_def": True}, seed=100) + attrs={"first_opp_def": True}, seed=1) + def test_strategy4(self): # Against a random opponent, will respond mostly randomly too. actions = [(C, C), (C, C), From f0e0713089fa29d9cafe13c5a4ea12a9f22fa4af Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sat, 4 Jul 2020 11:39:39 -0700 Subject: [PATCH 042/121] Rework tests in axlrod_second --- .../tests/strategies/test_axelrod_second.py | 531 ++++++------------ 1 file changed, 173 insertions(+), 358 deletions(-) diff --git a/axelrod/tests/strategies/test_axelrod_second.py b/axelrod/tests/strategies/test_axelrod_second.py index b20f0a614..3da7d255a 100644 --- a/axelrod/tests/strategies/test_axelrod_second.py +++ b/axelrod/tests/strategies/test_axelrod_second.py @@ -50,30 +50,31 @@ class TestEatherley(TestPlayer): "manipulates_state": False, } - def test_strategy(self): + def test_reciprocate_cooperation(self): # Test cooperate after opponent cooperates actions = [(C, C)] * 5 self.versus_test(axl.Cooperator(), expected_actions=actions) + def test_defect_again_defector(self): # If opponent only defects then probability of cooperating is 0. actions = [(C, D), (D, D), (D, D), (D, D), (D, D)] self.versus_test(axl.Defector(), expected_actions=actions) - def test_stocastic_response1(self): + def test_stochastic_response1(self): # Stochastic response to defect actions = [(C, C), (C, D), (D, C), (C, D), (D, C)] self.versus_test(axl.Alternator(), expected_actions=actions, seed=10) - def test_stocastic_response2(self): + def test_stochastic_response2(self): actions = [(C, C), (C, D), (C, C), (C, D), (D, C)] self.versus_test(axl.Alternator(), expected_actions=actions, seed=1) - def test_stocastic_response3(self): + def test_stochastic_response3(self): opponent = axl.MockPlayer(actions=[D, C, C, D]) actions = [(C, D), (D, C), (C, C), (C, D), (C, D)] self.versus_test(opponent, expected_actions=actions, seed=1) - def test_stocastic_response4(self): + def test_stochastic_response4(self): opponent = axl.MockPlayer(actions=[D, C, C, D]) actions = [(C, D), (D, C), (C, C), (C, D), (D, D)] self.versus_test(opponent, expected_actions=actions, seed=2) @@ -169,8 +170,8 @@ class TestTranquilizer(TestPlayer): "manipulates_state": False, } - def test_init(self): - # test for initalised variables + def test_initialised_variables(self): + # test for initialised variables player = axl.SecondByTranquilizer() self.assertEqual(player.num_turns_after_good_defection, 0) self.assertEqual(player.opponent_consecutive_defections, 0) @@ -546,10 +547,11 @@ class TestKluepfel(TestPlayer): "manipulates_state": False, } - def test_strategy(self): + def test_cooperates_with_cooperator(self): actions = [(C, C)] * 100 # Cooperate forever self.versus_test(axl.Cooperator(), expected_actions=actions) + def test_versus_alternator(self): # Since never two in a row, will respond in kind with 70% if # coop and 60% otherwise, after first couple actions = [ @@ -563,71 +565,21 @@ def test_strategy(self): ] self.versus_test(axl.Alternator(), expected_actions=actions, seed=36) - def test_strategy2(self): + def test_versus_alternator2(self): actions = [(C, C), (C, D), (C, C), (D, D), (D, C), (C, D)] self.versus_test(axl.Alternator(), expected_actions=actions, seed=10) - def test_strategy3(self): + def test_versus_alternator3(self): actions = [(C, C), (C, D), (D, C), (C, D), (D, C), (C, D), (C, C)] self.versus_test(axl.Alternator(), expected_actions=actions, seed=3) - def test_strategy4(self): + def test_detect_random(self): # Now we have to test the detect-random logic, which doesn't pick up - # until after 26 turns. So we need a big sample. - actions = [ - (C, D), - (D, D), - (D, D), - (D, D), - (D, D), - (D, C), - (C, C), - (C, D), - (C, C), - (D, D), - (D, C), - (C, C), - (C, D), - (D, D), - (C, D), - (D, D), - (D, C), - (C, C), - (D, C), - (C, C), - (C, D), - (D, D), - (D, C), - (C, D), - (D, C), - (C, C), - (C, D), - # Success detect random opponent for remaining turns. - (D, D), - (D, D), - (D, D), - (D, C), - (D, D), - (D, C), - (D, D), - (D, C), - (D, D), - (D, C), - (D, C), - (D, D), - (D, D), - (D, C), - (D, C), - (D, C), - (D, C), - (D, D), - (D, C), - (D, C), - (D, C), - (D, C), - (D, D), - ] - self.versus_test(axl.Random(0.5), expected_actions=actions, seed=10) + # until after 26 turns. + match = axl.Match((self.player(), axl.Random(0.5)), turns=40, seed=15) + result = match.play() + player_history = [turn[0] for turn in result] + self.assertEqual(player_history[27:], [D] * 13) class TestBorufsen(TestPlayer): @@ -693,111 +645,54 @@ class TestCave(TestPlayer): "manipulates_state": False, } - def test_strategy(self): + def test_cooperate_against_cooperator(self): + """Strategy always cooperates if the opponent has always cooperated.""" actions = [(C, C)] * 100 - self.versus_test(axl.Cooperator(), expected_actions=actions) - - # It will take until turn 18 to respond decide to respond D->D - actions = [(C, D)] - actions += [ - (C, D), - (D, D), - (D, D), - (C, D), - (C, D), - (C, D), - (D, D), - (D, D), - (C, D), - (C, D), - (D, D), - (C, D), - (D, D), - (C, D), - (C, D), - (D, D), - (C, D), - ] # Randomly choose - actions += [(D, D)] * 30 # Defect - self.versus_test(axl.Defector(), expected_actions=actions, seed=1) - - def test_strategy2(self): - # Highly-defective opponent - # It will take until turn 20 to respond decide to repond D to C + self.versus_test(axl.Cooperator(), expected_actions=actions, seed=1) + + def test_stochastic_behavior(self): + """Test random responses on turns 1 through 17.""" + # Use an explicit match because the random behavior in turns 1 through 17 + # makes finding seeds reproducibly difficult. + match = axl.Match((axl.SecondByCave(), axl.Alternator()), turns=30, seed=1) + match.play() + player_history = [round[0] for round in match.result] + self.assertTrue(C in player_history[1:17]) + self.assertTrue(D in player_history[1:17]) + + def test_serial_defection_against_defector(self): + """Against defector, it will take until turn 18 to respond decide + to respond D->D.""" + # Use an explicit match because the random behavior in turns 1 through 17 + # makes finding seeds reproducibly difficult. + match = axl.Match((axl.SecondByCave(), axl.Defector()), turns=30, seed=1) + result = match.play() + self.assertEqual(result[0], (C, D)) + self.assertEqual(result[18:], [(D, D)] * 12) + + def test_serial_defection_against_mostly_defector(self): + """Against a mostly defecting strategy, it will take until turn 20 to + respond decide to respond D->D, with a cooperation in between.""" + # Use an explicit match because the random behavior in turns 1 through 17 + # makes finding seeds reproducibly difficult. opponent_actions = [D] * 17 + [C, C, C, C] almost_defector = axl.MockPlayer(actions=opponent_actions) - - actions = [(C, D)] - actions += [ - (C, D), - (D, D), - (D, D), - (C, D), - (C, D), - (C, D), - (D, D), - (D, D), - (C, D), - (C, D), - (D, D), - (C, D), - (D, D), - (C, D), - (C, D), - (D, D), - (C, C), - ] # Randomly choose - actions += [(C, C)] # Coop for a minute - actions += [(D, C), (D, C)] - self.versus_test(almost_defector, expected_actions=actions, seed=1) - - def test_strategy3(self): - # Here it will take until turn 40 to detect random and defect - actions = [(C, C)] - actions += [ - (C, D), - (D, C), - (C, D), - (D, C), - (C, D), - (C, C), - (C, D), - (C, C), - (C, D), - (D, C), - (C, D), - (D, C), - (C, D), - (D, C), - (C, D), - (C, C), - (C, D), - (D, C), - (C, D), - (D, C), - (C, D), - (D, C), - (C, D), - (C, C), - (C, D), - (C, C), - (C, D), - (C, C), - (C, D), - (D, C), - (C, D), - (D, C), - (C, D), - (D, C), - (C, D), - ] # Randomly choose - actions += [ - (D, C), - (C, D), - (D, C), - ] # 17 D have come, so tit for tat for a while - actions += [(D, D), (D, C)] * 100 # Random finally detected - self.versus_test(axl.Alternator(), expected_actions=actions, seed=114940) + match = axl.Match((axl.SecondByCave(), almost_defector), turns=21, seed=1) + result = match.play() + self.assertEqual(result[0], (C, D)) + self.assertEqual(result[-3], (C, C)) + self.assertEqual(result[-2:], [(D, C), (D, C)]) + + def test_versus_alternator(self): + """Against Altenator, it will take until turn 40 to detect + random and defect.""" + # Use an explicit match because the random behavior in turns 1 through 17 + # makes finding seeds reproducibly difficult. + match = axl.Match((axl.SecondByCave(), axl.Alternator()), turns=100, seed=1) + result = match.play() + self.assertEqual(result[0], (C, C)) + self.assertEqual(result[37: 40], [(C, D), (D, C), (D, D)]) + self.assertEqual(result[40:], [(D, C), (D, D)] * 30) class TestWmAdams(TestPlayer): @@ -1005,7 +900,7 @@ class TestHarrington(TestPlayer): "manipulates_state": False, } - def test_strategy(self): + def test_fair_weather_flag(self): # Build an opponent that will cooperate the first 36 turns and # defect on the 37th turn opponent_actions = [C] * 36 + [D] + [C] * 100 @@ -1016,7 +911,7 @@ def test_strategy(self): Defect37, expected_actions=actions, attrs={"mode": "Fair-weather"} ) - def test_strategy2(self): + def test_exit_fair_weather(self): # Defect on 37th turn to activate Fair-weather, then later defect to # exit Fair-weather opponent_actions = [C] * 36 + [D] + [C] * 100 + [D] + [C] * 4 @@ -1029,7 +924,7 @@ def test_strategy2(self): Defect37_big, expected_actions=actions, seed=10, attrs={"mode": "Normal"} ) - def test_strategy3(self): + def test_exit_fair_weather2(self): actions = [(C, C)] * 36 + [(D, D)] + [(C, C)] * 100 actions += [(C, D)] # Immediately exit Fair-weather @@ -1040,7 +935,7 @@ def test_strategy3(self): Defect37_big, expected_actions=actions, seed=1, attrs={"mode": "Normal"} ) - def test_strategy4(self): + def test_non_fair_weather(self): # Opponent defects on 1st turn opponent_actions = [D] + [C] * 46 Defect1 = axl.MockPlayer(actions=opponent_actions) @@ -1055,7 +950,7 @@ def test_strategy4(self): actions += [(D, C), (C, C), (C, C)] self.versus_test(Defect1, expected_actions=actions, seed=19) - def test_strategy5(self): + def test_turn37_defection(self): # Defection on turn 37 by opponent doesn't have an effect here opponent_actions = [D] + [C] * 35 + [D] + [C] * 10 Defect1_37 = axl.MockPlayer(actions=opponent_actions) @@ -1066,7 +961,7 @@ def test_strategy5(self): actions += [(D, C), (C, C), (C, C)] self.versus_test(Defect1_37, expected_actions=actions, seed=19) - def test_strategy6(self): + def test_turn38_defection(self): # However a defect on turn 38 would be considered a burn. opponent_actions = [D] + [C] * 36 + [D] + [C] * 9 Defect1_38 = axl.MockPlayer(actions=opponent_actions) @@ -1080,7 +975,7 @@ def test_strategy6(self): Defect1_38, expected_actions=actions, seed=2, attrs={"burned": True} ) - def test_strategy7(self): + def test_parity_flags(self): # Use alternator to test parity flags. actions = [(C, C), (C, D)] # Even streak is set to 2, one for the opponent's defect and one for @@ -1102,6 +997,7 @@ def test_strategy7(self): actions += [(C, D), (D, C), (C, D), (D, C), (C, D), (C, C)] self.versus_test(axl.Alternator(), expected_actions=actions, seed=10) + def test_parity_limit_shortening(self): # Test for parity limit shortening. opponent_actions = [D, C] * 1000 AsyncAlternator = axl.MockPlayer(actions=opponent_actions) @@ -1117,9 +1013,11 @@ def test_strategy7(self): # Now hit the limit sooner actions += [(C, D), (D, C), (C, D), (C, C)] * 5 self.versus_test( - AsyncAlternator, expected_actions=actions, attrs={"parity_limit": 3} + AsyncAlternator, expected_actions=actions, attrs={"parity_limit": 3}, + seed=10 ) + def test_detect_streak(self): # Use a Defector to test the 20-defect streak actions = [(C, D), (D, D), (D, D), (D, D), (D, D)] # Now the two parity flags are used @@ -1136,57 +1034,21 @@ def test_strategy7(self): axl.Defector(), expected_actions=actions, attrs={"recorded_defects": 119}, + seed=10 ) - def test_strategy8(self): - # Detect random - expected_actions = [ - (C, D), - (D, C), - (C, D), - (D, C), - (C, D), - (C, D), - (D, D), - (D, C), - (C, D), - (D, C), - (C, C), - (C, D), - (D, D), - (D, C), - (C, D), - (D, D), - (D, C), - (C, C), - (C, D), - (D, C), - (C, D), - (D, D), - (D, C), - (C, D), - (D, D), - (D, D), - (C, D), - (D, C), - (C, C), - ] - # Enter defect mode. - expected_actions += [(D, C)] - - self.versus_test(axl.Random(), expected_actions=expected_actions, - turns=len(expected_actions), seed=10) - - player = self.player() - match = axl.Match((player, axl.Random()), turns=len(expected_actions), seed=10) - # The history matrix will be [[0, 2], [5, 6], [3, 6], [4, 2]] - actions = match.play() - self.assertEqual(actions, expected_actions) - self.assertAlmostEqual( - player.calculate_chi_squared(len(expected_actions)), 2.395, places=3 - ) - - def test_strategy9(self): + def test_detect_random(self): + """Tests that detect_random() is triggered on a Random opponent and + that the strategy defects thereafter.""" + match = axl.Match((axl.SecondByHarrington(), axl.Random()), seed=10, + turns=31) + match.play() + player = match.players[0] + # Check that detect_random(30) is True. + self.assertTrue(player.detect_random(30)) + self.assertTrue(player.calculate_chi_squared(31) < 3) + + def test_exit_defect_mode(self): # Come back out of defect mode opponent_actions = [ D, @@ -1460,62 +1322,23 @@ class TestWhite(TestPlayer): "manipulates_state": False, } - def test_strategy(self): + def test_cooperates_with_cooperator(self): actions = [(C, C)] * 30 self.versus_test(axl.Cooperator(), expected_actions=actions) + def test_defects_on_turn_10_against_defector(self): actions = [(C, D)] * 10 + [(D, D)] * 20 self.versus_test(axl.Defector(), expected_actions=actions) - def test_strategy2(self): - actions = [ - (C, D), - (C, D), - (C, C), - (C, C), - (C, C), - (C, D), - (C, C), - (C, D), - (C, C), - (C, D), - (C, C), - (C, D), - (C, D), - (D, C), - (C, D), - (D, D), - (D, C), - (C, D), - (D, D), - (D, C), - ] - self.versus_test(axl.Random(0.5), expected_actions=actions, seed=6) + def test_defection_logic_triggered(self): + actions = [(C, D), (C, D), (C, C), (C, D), (C, D), (C, C), (C, D), (C, D), + (C, C), (C, D), (D, D), (D, C), (C, D), (D, D), (D, C), (C, D), + (D, D), (D, C), (C, D), (D, D)] + self.versus_test(axl.CyclerDDC(), expected_actions=actions) - def test_strategy3(self): - actions = [ - (C, C), - (C, D), - (C, D), - (C, C), - (C, C), - (C, C), - (C, C), - (C, D), - (C, D), - (C, D), - (C, D), - (D, D), - (D, C), - (C, C), - (C, C), - (C, D), - (C, C), - (C, D), - (C, C), - (C, D), - ] - self.versus_test(axl.Random(0.5), expected_actions=actions, seed=12) + def test_defection_logic_not_triggered(self): + actions = [(C, C), (C, D)] * 10 + self.versus_test(axl.Alternator(), expected_actions=actions, seed=12) class TestBlack(TestPlayer): @@ -1959,12 +1782,82 @@ class TestAppold(TestPlayer): "manipulates_state": False, } - def test_strategy(self): - # Should cooperate 100% of the time with the cooperator + def test_cooperate_against_cooperating_opponent(self): + """Strategy should cooperate 100% of the time with a fully cooperating opponent.""" actions = [(C, C)] * 100 - self.versus_test(axl.Cooperator(), expected_actions=actions) + self.versus_test(axl.Cooperator(), expected_actions=actions, + attrs={ + "first_opp_def": False, + "total_num_of_x": {C: 99, D: 1}, + "opp_c_after_x": {C: 99, D: 1} + }) + + def test_cooperate_on_first_four_turns(self): + """Strategy will cooperate on the first four turns regardless of opponent.""" + # Hypothesis opportunity: choose random opponent + player_expected_actions = [C, C, C, C] + coplayer_expected_actions = [D, D, D, D] + expected_actions = list(zip(player_expected_actions, coplayer_expected_actions)) + self.versus_test(axl.Defector(), turns=4, expected_actions=expected_actions, + attrs={ + "first_opp_def": False, + "total_num_of_x": {C: 3, D: 1}, + "opp_c_after_x": {C: 0, D: 1} + }) + + def test_fifth_move_cooperate(self): + """Strategy will cooperate on a fifth move defection and set first_opp_def.""" + player_expected_actions = [C, C, C, C, C, C] + coplayer_expected_actions = [C, C, C, C, D, C] + coplayer = axl.MockPlayer(actions=coplayer_expected_actions) + expected_actions = list(zip(player_expected_actions, coplayer_expected_actions)) + self.versus_test(coplayer, turns=6, expected_actions=expected_actions, + attrs={ + "first_opp_def": True, + "total_num_of_x": {C: 5, D: 1}, + "opp_c_after_x": {C: 4, D: 1} + }) + + def test_sixth_move_cooperate(self): + """Strategy will cooperate on a sixth move defection if it is the first.""" + player_expected_actions = [C, C, C, C, C, C, C] + coplayer_expected_actions = [C, C, C, C, C, D, C] + coplayer = axl.MockPlayer(actions=coplayer_expected_actions) + expected_actions = list(zip(player_expected_actions, coplayer_expected_actions)) + self.versus_test(coplayer, turns=7, expected_actions=expected_actions, seed=1, + attrs={ + "first_opp_def": True, + "total_num_of_x": {C: 6, D: 1}, + "opp_c_after_x": {C: 5, D: 1} + }) + + def test_sixth_move_defect(self): + """Strategy will defect on a sixth move defection if it is not the first.""" + player_expected_actions = [C, C, C, C, C, C, D] + coplayer_expected_actions = [C, C, C, C, D, D, C] + coplayer = axl.MockPlayer(actions=coplayer_expected_actions) + expected_actions = list(zip(player_expected_actions, coplayer_expected_actions)) + self.versus_test(coplayer, turns=7, expected_actions=expected_actions, seed=10, + attrs={ + "first_opp_def": True, + "total_num_of_x": {C: 6, D: 1}, + "opp_c_after_x": {C: 4, D: 1} + }) + + def test_later_single_defection_forgiveness(self): + # An opponent who defects after a long time, then tries cooperating + opponent_actions = [C] * 30 + [D] + [C] * 10 + MostlyCooperates = axl.MockPlayer(actions=opponent_actions) + # Cooperate always at first + actions = [(C, C)] * 30 + # The opponent defects once + actions += [(C, D)] + # But we forgive it (and record it). + actions += [(C, C)] * 10 + self.versus_test(MostlyCooperates, expected_actions=actions, seed=1, + attrs={"first_opp_def": True}) - def test_strategy2(self): + def test_stochastic_behavior(self): opponent = axl.Defector() # Cooperate always the first 4 turns actions = [(C, D)] * 4 @@ -1989,84 +1882,6 @@ def test_strategy2(self): (C, D), (C, D), (D, D), - (D, D), - (D, D), - (D, D), - (D, D), - (C, D), - (D, D), - (D, D), - (D, D), - (D, D), - (D, D), - (D, D), - (C, D), - (C, D), - (D, D), - (D, D)] - self.versus_test(opponent, expected_actions=actions, seed=1, + ] + self.versus_test(opponent, expected_actions=actions, seed=1018, attrs={"first_opp_def": True}) - - def test_strategy3(self): - # An opponent who defects for a long time, then tries cooperating - opponent_actions = [C] * 30 + [D] + [C] * 10 - MostlyCooperates = axl.MockPlayer(actions=opponent_actions) - # Cooperate always at first - actions = [(C, C)] * 30 - # The opponent defects once - actions += [(C, D)] - # But we forgive it. - actions += [(C, C)] * 10 - self.versus_test(MostlyCooperates, expected_actions=actions) - - opponent = axl.CyclerDC() - # First three opponent actions get counted as reactions to C. Fourth - # action will get counted on next turn. - actions = [(C, D), (C, C), (C, D), (C, C)] - self.versus_test(opponent, expected_actions=actions, - attrs={"opp_c_after_x": {C: 1, D: 1}, - "total_num_of_x": {C: 3, D: 1}}) - # Will cooperate 50% of the time - actions += [(C, D)] - self.versus_test(opponent, expected_actions=actions, - attrs={"opp_c_after_x": {C: 2, D: 1}, - "total_num_of_x": {C: 4, D: 1}, - "first_opp_def": False}, seed=1) - # Always cooperate, because we forgive the first defect - actions += [(C, C)] - self.versus_test(opponent, expected_actions=actions, - attrs={"first_opp_def": True}, seed=1) - - def test_strategy4(self): - # Against a random opponent, will respond mostly randomly too. - actions = [(C, C), - (C, C), - (C, D), - (C, C), - (C, C), - (C, D), - (C, C), - (C, C), - (C, C), - (D, C), - (C, D), - (D, D), - (C, D), - (C, D), - (C, C), - (C, C), - (D, C), - (C, D), - (D, D), - (C, C), - (C, D), - (C, C), - (C, C), - (C, D), - (D, C), - (C, D), - (D, D), - (C, D), - (C, C), - (D, C)] - self.versus_test(axl.Random(0.5), expected_actions=actions, seed=7) From 9c6da82bc6df09cef764403eb2fb8aeb6996dfdf Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sat, 4 Jul 2020 11:45:41 -0700 Subject: [PATCH 043/121] Update seeds in test_axelrod_first --- .../tests/strategies/test_axelrod_first.py | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/axelrod/tests/strategies/test_axelrod_first.py b/axelrod/tests/strategies/test_axelrod_first.py index d36e747c3..02825fd88 100644 --- a/axelrod/tests/strategies/test_axelrod_first.py +++ b/axelrod/tests/strategies/test_axelrod_first.py @@ -21,23 +21,31 @@ class TestFirstByDavis(TestPlayer): "manipulates_state": False, } - def test_strategy(self): - # Cooperates for the first ten rounds + def test_cooperate_first_ten_rounds1(self): + """Cooperates for the first ten rounds.""" actions = [(C, C)] * 10 self.versus_test(axl.Cooperator(), expected_actions=actions) + def test_cooperate_first_ten_rounds2(self): + """Cooperates for the first ten rounds.""" actions = [(C, D)] * 10 self.versus_test(axl.Defector(), expected_actions=actions) + def test_cooperate_first_ten_rounds3(self): + """Cooperates for the first ten rounds.""" actions = [(C, C), (C, D)] * 5 self.versus_test(axl.Alternator(), expected_actions=actions) - # If opponent defects at any point then the player will defect forever - # (after 10 rounds) + def test_retaliation_after_ten_rounds1(self): + """If opponent defects at any point then the player will defect forever + (after 10 rounds)""" opponent = axl.MockPlayer(actions=[C] * 10 + [D]) actions = [(C, C)] * 10 + [(C, D), (D, C)] self.versus_test(opponent, expected_actions=actions) + def test_retaliation_after_ten_rounds2(self): + """If opponent defects at any point then the player will defect forever + (after 10 rounds)""" opponent = axl.MockPlayer(actions=[C] * 15 + [D]) actions = [(C, C)] * 15 + [(C, D), (D, C)] self.versus_test(opponent, expected_actions=actions) @@ -57,13 +65,15 @@ class TestFirstByDowning(TestPlayer): "manipulates_state": False, } - def test_strategy(self): + def test_defect_first_two_rounds1(self): actions = [(D, C), (D, C), (C, C)] self.versus_test(axl.Cooperator(), expected_actions=actions) + def test_defect_first_two_rounds2(self): actions = [(D, D), (D, D), (D, D)] self.versus_test(axl.Defector(), expected_actions=actions) + def test_strategy(self): opponent = axl.MockPlayer(actions=[D, C, C]) actions = [(D, D), (D, C), (D, C), (D, D)] self.versus_test(opponent, expected_actions=actions) @@ -126,13 +136,16 @@ def test_decay(self): match.play() self.assertEqual(player._cooperation_probability(), player._end_coop_prob) - def test_strategy(self): + def test_stochastic_behavior(self): actions = [(C, C)] * 13 + [(D, C)] self.versus_test(axl.Cooperator(), expected_actions=actions, seed=1) + def test_stochastic_behavior2(self): actions = [(C, C)] * 11 + [(D, C)] self.versus_test(axl.Cooperator(), expected_actions=actions, seed=2) + def test_against_defector(self): + """Behavior is deterministic since opponent always defects.""" actions = [(C, D)] + [(D, D)] * 20 self.versus_test(axl.Defector(), expected_actions=actions) @@ -283,19 +296,18 @@ def test_four_vector(self): expected_dictionary = {(C, C): 0.9, (C, D): 0, (D, C): 0.9, (D, D): 0} test_four_vector(self, expected_dictionary) - def test_strategy(self): + def test_stochastic_behavior(self): actions = [(C, C), (C, C), (C, C), (D, C)] self.versus_test(axl.Cooperator(), expected_actions=actions, seed=1) + def test_stochastic_behavior2(self): actions = [(C, C), (C, C), (C, C), (C, C)] self.versus_test(axl.Cooperator(), expected_actions=actions, seed=2) + def test_stochastic_behavior3(self): actions = [(C, D), (D, D), (D, D), (D, D)] self.versus_test(axl.Defector(), expected_actions=actions, seed=1) - actions = [(C, D), (D, D), (D, D), (D, D)] - self.versus_test(axl.Defector(), expected_actions=actions, seed=2) - class TestFirstByNydegger(TestPlayer): @@ -443,10 +455,11 @@ class TestFirstByAnonymous(TestPlayer): "manipulates_state": False, } - def test_strategy(self): + def test_stochastic_behavior(self): actions = [(D, C), (C, C), (C, C), (C, C), (D, C), (C, C)] self.versus_test(axl.Cooperator(), expected_actions=actions, seed=1) + def test_stochastic_behavior2(self): actions = [(D, C), (D, C), (D, C), (C, C), (D, C), (D, C)] self.versus_test(axl.Cooperator(), expected_actions=actions, seed=10) From e308d21b67ee1f4610537caa369a02aac94918bf Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sat, 4 Jul 2020 12:12:00 -0700 Subject: [PATCH 044/121] Fix alternator test --- axelrod/tests/strategies/test_alternator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axelrod/tests/strategies/test_alternator.py b/axelrod/tests/strategies/test_alternator.py index 98ecde394..b4e4054cc 100644 --- a/axelrod/tests/strategies/test_alternator.py +++ b/axelrod/tests/strategies/test_alternator.py @@ -30,6 +30,6 @@ def test_versus_defector(self): self.versus_test(axl.Defector(), expected_actions=actions) def test_versus_cycler_DC(self): - opponent = axl.CyclerDC + opponent = axl.CyclerDC() actions = [(C, D), (D, C)] * 5 self.versus_test(opponent, expected_actions=actions) From eaf2d794751de2220ba218dd9713f8ef55adb00b Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sat, 4 Jul 2020 12:12:48 -0700 Subject: [PATCH 045/121] Fix Various Typos --- axelrod/strategies/axelrod_second.py | 8 ++++---- axelrod/strategies/meta.py | 2 +- axelrod/strategies/qlearner.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/axelrod/strategies/axelrod_second.py b/axelrod/strategies/axelrod_second.py index 1ed19269b..0db891e6c 100644 --- a/axelrod/strategies/axelrod_second.py +++ b/axelrod/strategies/axelrod_second.py @@ -749,8 +749,8 @@ class SecondByCave(Player): - turn > 29 and percent defects > 0.65 - turn > 19 and percent defects > 0.79 - Otherwise, respond to cooperation with cooperation. And respond to defcts - with either a defect (if opponent has defected at least 18 times) or with + Otherwise, respond to cooperation with cooperation. And respond to defections + with either a defectection (if opponent has defected at least 18 times) or with a random (50/50) choice. [Cooperate on first.] Names: @@ -1546,8 +1546,8 @@ class SecondByWhite(Player): Strategy submitted to Axelrod's second tournament by Edward C White (K72R) and came in thirteenth in that tournament. - * If the opponent Cooperated last turn or in the first ten turns, then - Cooperate. + * Cooperate in the first ten turns + * If the opponent Cooperated last turn then Cooperate. * Otherwise Defect if and only if: floor(log(turn)) * opponent Defections >= turn diff --git a/axelrod/strategies/meta.py b/axelrod/strategies/meta.py index 37b586b90..33a914042 100644 --- a/axelrod/strategies/meta.py +++ b/axelrod/strategies/meta.py @@ -182,7 +182,7 @@ def update_histories(self, coplay): self._update_scores(coplay) def meta_strategy(self, results, opponent): - # Choice an action based on the collection of scores + # Choose an action based on the collection of scores bestscore = max(self.scores) beststrategies = [ i for (i, score) in enumerate(self.scores) if score == bestscore diff --git a/axelrod/strategies/qlearner.py b/axelrod/strategies/qlearner.py index f925ccdf0..b0690c06d 100644 --- a/axelrod/strategies/qlearner.py +++ b/axelrod/strategies/qlearner.py @@ -41,7 +41,7 @@ def __init__(self) -> None: super().__init__() - # Set this explicitely, since the constructor of super will not pick it up + # Set this explicitly, since the constructor of super will not pick it up # for any subclasses that do not override methods using random calls. self.classifier["stochastic"] = True From 7811434da31e64de74699457c4142880723fb2d0 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sat, 4 Jul 2020 12:13:19 -0700 Subject: [PATCH 046/121] Dataclasses and yaml parsing of versus_test --- axelrod/data_classes.py | 58 ++++++++++++++++++++++++++++++++--- axelrod/yaml.py | 67 ++++++++++++++++++++++++++++++----------- 2 files changed, 103 insertions(+), 22 deletions(-) diff --git a/axelrod/data_classes.py b/axelrod/data_classes.py index d3f0c3a09..2f8c1857c 100644 --- a/axelrod/data_classes.py +++ b/axelrod/data_classes.py @@ -6,6 +6,26 @@ from axelrod.action import Action +def verify_match_outcomes(match, expected_actions1, expected_actions2, attrs): + """Tests that match produces the expected results.""" + match.play() + player1, player2 = match.players + for (play, expected_play) in zip(player1.history, expected_actions1): + if play != expected_play: + # print(play, expected_play) + return False + for (play, expected_play) in zip(player2.history, expected_actions2): + # print(play, expected_play) + if play != expected_play: + return False + # Test final player attributes are as expected + if attrs: + for attr, value in attrs.items(): + if getattr(player1, attr) != value: + return False + return True + + @dataclass class PlayerConfig: name: str @@ -26,6 +46,7 @@ class MatchParameters: prob_end: Optional[float] = None seed: Optional[int] = None game: Optional[axelrod.Game] = None + match_attributes: Optional[dict] = None @dataclass @@ -42,20 +63,49 @@ class MatchConfig: match_parameters: MatchParameters expected_outcome: Optional[ExpectedMatchOutcome] = None - def __call__(self): + def match(self, seed=None): """Generate the match.""" player = self.player() coplayer = self.coplayer() noise = self.match_parameters.noise + if not seed: + seed = self.match_parameters.seed prob_end = self.match_parameters.prob_end + match_attributes = self.match_parameters.match_attributes turns = len(self.expected_outcome.player_actions) match = axelrod.Match( (player, coplayer), turns=turns, noise=noise, - prob_end=prob_end + prob_end=prob_end, + seed=seed, + match_attributes=match_attributes ) return match - def test_match(self): - pass + def verify_match_outcomes(self, seed=None): + match = self.match(seed=seed) + verify_match_outcomes(match, + self.expected_outcome.player_actions, + self.expected_outcome.coplayer_actions, + self.expected_outcome.player_attributes) + + def search_seeds(self, lower=1, upper=100000): + """Searches for a working seed.""" + for seed in range(lower, upper): + match = self.match(seed=seed) + if verify_match_outcomes(match, + self.expected_outcome.player_actions, + self.expected_outcome.coplayer_actions, + self.expected_outcome.player_attributes): + return seed + return None + + +@dataclass +class TestMatchConfig: + name: str + description: Optional[str] + match_config: MatchConfig + + diff --git a/axelrod/yaml.py b/axelrod/yaml.py index e6fa054dd..593e09f10 100644 --- a/axelrod/yaml.py +++ b/axelrod/yaml.py @@ -1,4 +1,5 @@ from dataclasses import asdict +import inspect from typing import Tuple from dacite import from_dict, Config @@ -6,38 +7,44 @@ import axelrod from axelrod.action import Action, actions_to_str, str_to_actions -from axelrod.data_classes import ExpectedMatchOutcome, PlayerConfig, MatchConfig, MatchParameters +from axelrod.data_classes import ExpectedMatchOutcome, PlayerConfig, MatchConfig, MatchParameters, TestMatchConfig filename = "test_matches.yaml" +# f = open("test_matches.yaml", 'w') +# f.close() def build_player_spec(name, init_kwargs=None): - if name == "MockPlayer": - init_kwargs["actions"] = actions_to_str(init_kwargs["actions"]) + # if name == "MockPlayer": + # init_kwargs["actions"] = actions_to_str(init_kwargs["actions"]) return PlayerConfig(name=name, init_kwargs=dict(init_kwargs)) -def build_expected_spec(player_actions, coplayer_actions, attr=None): +def build_expected_spec(player_actions, coplayer_actions, player_attributes=None): return ExpectedMatchOutcome( player_actions=player_actions, coplayer_actions=coplayer_actions, - player_attributes=attr) + player_attributes=player_attributes) -def build_match_parameters_spec(noise=None, seed=None): - return MatchParameters(noise=noise, seed=seed) +def build_match_parameters_spec(noise=None, seed=None, match_attributes=None): + return MatchParameters(noise=noise, seed=seed, match_attributes=match_attributes) def build_match_spec(player_name, coplayer_name, player_actions, coplayer_actions, noise=None, seed=None, - player_init_kwargs=None, coplayer_init_kwargs=None, attr=None): + player_init_kwargs=None, coplayer_init_kwargs=None, player_attributes=None, match_attributes=None): return MatchConfig( player=build_player_spec(player_name, init_kwargs=player_init_kwargs.copy()), coplayer=build_player_spec(coplayer_name, init_kwargs=coplayer_init_kwargs.copy()), - match_parameters=build_match_parameters_spec(noise=noise, seed=seed), - expected_outcome=build_expected_spec(player_actions, coplayer_actions, attr=attr) + match_parameters=build_match_parameters_spec(noise=noise, seed=seed, match_attributes=match_attributes), + expected_outcome=build_expected_spec(player_actions, coplayer_actions, player_attributes=player_attributes) ) +def build_test_match_config(name, description, match_config): + return TestMatchConfig(name=name, description=description, match_config=match_config) + + def log_kwargs(func): def wrapper(*args, **kwargs): try: @@ -48,15 +55,39 @@ def wrapper(*args, **kwargs): seed = kwargs["seed"] except KeyError: seed = None - - spec = build_match_spec(str(args[1].__class__.__name__), str(args[2].__class__.__name__), - actions_to_str(args[-2]), actions_to_str(args[-1]), - noise=noise, seed=seed, - player_init_kwargs=args[1].init_kwargs, - coplayer_init_kwargs=args[2].init_kwargs) + try: + match_attributes = kwargs["match_attributes"] + except KeyError: + match_attributes = None + try: + player_attributes = kwargs["attrs"] + except KeyError: + player_attributes = None + + # Some inspect shenanigans to get the calling function name and docstring + outer_frame = inspect.getouterframes(inspect.currentframe(), context=2) + calling_frame_info = outer_frame[2] + function_name = calling_frame_info.function + code = calling_frame_info.frame.f_code + f = getattr(calling_frame_info.frame.f_locals['self'], code.co_name) + docstring = inspect.getdoc(f) + + test_config = build_test_match_config( + name=function_name, + description=docstring, + match_config=build_match_spec( + str(args[1].__class__.__name__), + str(args[2].__class__.__name__), + actions_to_str(args[-2]), actions_to_str(args[-1]), + noise=noise, seed=seed, + player_init_kwargs=args[1].init_kwargs, + coplayer_init_kwargs=args[2].init_kwargs, + match_attributes=match_attributes, + player_attributes=player_attributes) + ) stream = open(filename, 'a') stream.write("---\n") - yaml.dump(asdict(spec), stream) + yaml.dump(asdict(test_config), stream) stream.close() return func(*args, **kwargs) return wrapper @@ -65,7 +96,7 @@ def wrapper(*args, **kwargs): def load_matches(): stream = open(filename, 'r') matches = yaml.load_all(stream, Loader=yaml.Loader) - return [from_dict(data_class=MatchConfig, data=match, config=Config( + return [from_dict(data_class=TestMatchConfig, data=match, config=Config( type_hooks={Tuple[Action, ...]: str_to_actions})) for match in matches] From 8a6930f652cb7a86a333db2785193c0159368625 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sat, 4 Jul 2020 12:16:42 -0700 Subject: [PATCH 047/121] Add deadline=None to hypothesis tests --- axelrod/tests/integration/test_filtering.py | 2 +- axelrod/tests/unit/test_resultset.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/axelrod/tests/integration/test_filtering.py b/axelrod/tests/integration/test_filtering.py index 00680ab20..4b0d9d2c2 100644 --- a/axelrod/tests/integration/test_filtering.py +++ b/axelrod/tests/integration/test_filtering.py @@ -50,7 +50,7 @@ def test_boolean_filtering(self, strategies): memory_depth=float("inf"), strategies=axl.short_run_time_strategies, ) - @settings(max_examples=5) + @settings(max_examples=5, deadline=None) def test_memory_depth_filtering( self, min_memory_depth, max_memory_depth, memory_depth, strategies ): diff --git a/axelrod/tests/unit/test_resultset.py b/axelrod/tests/unit/test_resultset.py index c2239a2cd..97563d3e9 100644 --- a/axelrod/tests/unit/test_resultset.py +++ b/axelrod/tests/unit/test_resultset.py @@ -1218,7 +1218,7 @@ class TestSummary(unittest.TestCase): @given( tournament=tournaments(min_size=2, max_size=5, max_turns=5, max_repetitions=3) ) - @settings(max_examples=5) + @settings(max_examples=5, deadline=None) def test_summarise_without_failure(self, tournament): results = tournament.play(progress_bar=False) sd = results.summarise() From add852fa6e77e3ff7d74640061896442ba794516 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sat, 4 Jul 2020 14:25:36 -0700 Subject: [PATCH 048/121] Fix several doc tests and seeds --- axelrod/moran.py | 7 +- axelrod/tournament.py | 3 +- docs/reference/glossary.rst | 15 ++-- docs/reference/overview_of_strategies.rst | 5 +- docs/tutorials/advanced/setting_a_seed.rst | 80 ++++++++++++++----- .../approximate_moran_processes.rst | 16 ++-- .../moran_processes_on_graphs.rst | 5 +- .../further_topics/spatial_tournaments.rst | 5 +- docs/tutorials/getting_started/moran.rst | 49 +++++------- .../running_axelrods_first_tournament.rst | 43 +++++----- 10 files changed, 134 insertions(+), 94 deletions(-) diff --git a/axelrod/moran.py b/axelrod/moran.py index af8d18e57..bf0f92768 100644 --- a/axelrod/moran.py +++ b/axelrod/moran.py @@ -86,6 +86,8 @@ def __init__( or based on the player's mutation method, if present ("atomic"). stop_on_fixation: A bool indicating if the process should stop on fixation + seed: int + A random seed for reproducibility """ self.turns = turns self.prob_end = prob_end @@ -476,7 +478,8 @@ class ApproximateMoranProcess(MoranProcess): """ def __init__( - self, players: List[Player], cached_outcomes: dict, mutation_rate: float = 0 + self, players: List[Player], cached_outcomes: dict, mutation_rate: float = 0, + seed: Optional[int] = None ) -> None: """ Parameters @@ -494,6 +497,7 @@ def __init__( noise=0, deterministic_cache=None, mutation_rate=mutation_rate, + seed=seed ) self.cached_outcomes = cached_outcomes @@ -512,7 +516,6 @@ def score_all(self) -> List: for i in range(N): for j in range(i + 1, N): player_names = tuple([str(self.players[i]), str(self.players[j])]) - cached_score = self._get_scores_from_cache(player_names) scores[i] += cached_score[0] scores[j] += cached_score[1] diff --git a/axelrod/tournament.py b/axelrod/tournament.py index 7e8f666ce..925fe1477 100644 --- a/axelrod/tournament.py +++ b/axelrod/tournament.py @@ -71,6 +71,7 @@ def __init__( self.players = players self.repetitions = repetitions self.edges = edges + self.seed = seed if turns is None and prob_end is None: turns = DEFAULT_TURNS @@ -86,7 +87,7 @@ def __init__( noise=self.noise, edges=edges, match_attributes=match_attributes, - seed=seed + seed=self.seed ) self._logger = logging.getLogger(__name__) diff --git a/docs/reference/glossary.rst b/docs/reference/glossary.rst index 396e0cec4..04427e88f 100644 --- a/docs/reference/glossary.rst +++ b/docs/reference/glossary.rst @@ -23,10 +23,8 @@ A **play** is a single player choosing an **action**. In terms of code this is equivalent to:: >>> p1, p2 = axl.Cooperator(), axl.Defector() - >>> (C, D) = p1.play(p2) # This constitues two 'plays' (p1 plays and p2 plays). - -This is equivalent to :code:`p2.play(p1)`. Either function invokes both -:code:`p1.strategy(p2)` and :code:`p2.strategy(p1)`. + >>> C = p1.strategy(p2) # This constitutes two 'plays' (p1 plays and p2 plays). + >>> D = p2.strategy(p1) # This constitutes two 'plays' (p1 plays and p2 plays). A turn ------ @@ -45,11 +43,10 @@ used in the tournament is 200. Here is a single match between two players over 3 turns:: >>> p1, p2 = axl.Cooperator(), axl.Defector() - >>> for turn in range(3): - ... p1.play(p2) - (C, D) - (C, D) - (C, D) + >>> match = axl.Match((p1, p2), turns=3) + >>> result = match.play() + >>> result + [(C, D), (C, D), (C, D)] >>> p1.history, p2.history ([C, C, C], [D, D, D]) diff --git a/docs/reference/overview_of_strategies.rst b/docs/reference/overview_of_strategies.rst index d7b2ce901..391b38052 100644 --- a/docs/reference/overview_of_strategies.rst +++ b/docs/reference/overview_of_strategies.rst @@ -427,15 +427,14 @@ The following code reproduces the above:: ... axl.OriginalGradual(), ... axl.WinStayLoseShift(), ... ] - >>> axl.seed(1) >>> turns = 1000 - >>> tournament = axl.Tournament(players, turns=turns, repetitions=1) + >>> tournament = axl.Tournament(players, turns=turns, repetitions=1, seed=1) >>> results = tournament.play(progress_bar=False) >>> for average_score_per_turn in results.payoff_matrix[-2]: ... print(round(average_score_per_turn * turns, 1)) 3000.0 915.0 - 2763.0 + 2756.0 3000.0 3000.0 2219.0 diff --git a/docs/tutorials/advanced/setting_a_seed.rst b/docs/tutorials/advanced/setting_a_seed.rst index 58158c43c..9f4b168d7 100644 --- a/docs/tutorials/advanced/setting_a_seed.rst +++ b/docs/tutorials/advanced/setting_a_seed.rst @@ -4,30 +4,74 @@ Setting a random seed ===================== The library has a variety of strategies whose behaviour is stochastic. To ensure -reproducible results a random seed should be set. As both Numpy and the standard -library are used for random number generation, both seeds need to be -set. To do this we can use the `seed` function:: +reproducible results a random seed should be set. The library abstracts away the +propagation of seeds in matches and tournaments, so you typically only need to +supply a seed to those objects. + +Matches +------- + +For a match, set a seed by passing a parameter to `Match` >>> import axelrod as axl >>> players = (axl.Random(), axl.MetaMixer()) # Two stochastic strategies - >>> axl.seed(0) - >>> results = axl.Match(players, turns=3).play() + >>> match = axl.Match(players, turns=3, seed=101) + >>> results = match.play() We obtain the same results if it is played with the same seed:: - >>> axl.seed(0) - >>> results == axl.Match(players, turns=3).play() + >>> match2 = axl.Match(players, turns=3, seed=101) + >>> result2 = match2.play() + >>> results == result2 True -Note that this is equivalent to:: - - >>> import numpy - >>> import random - >>> players = (axl.Random(), axl.MetaMixer()) - >>> random.seed(0) - >>> numpy.random.seed(0) - >>> results = axl.Match(players, turns=3).play() - >>> numpy.random.seed(0) - >>> random.seed(0) - >>> results == axl.Match(players, turns=3).play() +For noisy matches, a seed also needs to be set for reproducibility, even if the players are +deterministic. + + >>> import axelrod as axl + >>> players = (axl.Cooperator(), axl.Defector()) # Two deterministic strategies + >>> match = axl.Match(players, turns=200, seed=101, noise=0.25) + >>> results = match.play() + >>> match2 = axl.Match(players, turns=200, seed=101, noise=0.25) + >>> results2 = match2.play() + >>> results == results2 True + +Tournaments +----------- + +For tournaments, an initial seed is used to generate subsequent seeds for each match in a +a manner that will yield identical results. Note that if the tournament is run with multiple +processes, the order of the matches may be computed differently, but the seeds used for each +match will be the same. + +To seed a tournament we also pass a seed to the tournament at creation time: + + >>> import axelrod as axl + >>> seed = 201 + >>> players = (axl.Random(), axl.Cooperator(), axl.MetaMixer()) + >>> tournament = axl.Tournament(players, turns=5, repetitions=5, seed=seed) + >>> results = tournament.play(processes=1) + >>> tournament2 = axl.Tournament(players, turns=5, repetitions=5, seed=seed) + >>> results2 = tournament.play(processes=1) + >>> results.ranked_names == results2.ranked_names + True + +For parallel processing, the ordering of match results may differ, but the actual results, and the final +rankings, will be the same. + + >>> import axelrod as axl + >>> players = (axl.Random(), axl.Cooperator(), axl.MetaMixer()) + >>> tournament = axl.Tournament(players, turns=5, repetitions=5, seed=201) + >>> results = tournament.play(processes=2) + >>> tournament2 = axl.Tournament(players, turns=5, repetitions=5, seed=201) + >>> results2 = tournament.play(processes=2) + >>> results.ranked_names == results2.ranked_names + True + + +Moran Process +------------- + +Similarly, a Moran process is essentially another type of tournament. The library's implementation +will propagate child seeds to each match to ensure reproducibility. diff --git a/docs/tutorials/further_topics/approximate_moran_processes.rst b/docs/tutorials/further_topics/approximate_moran_processes.rst index 9150df476..e8d984007 100644 --- a/docs/tutorials/further_topics/approximate_moran_processes.rst +++ b/docs/tutorials/further_topics/approximate_moran_processes.rst @@ -19,19 +19,19 @@ the cache is built by passing counter objects of outcomes:: Now let us create an Approximate Moran Process:: - >>> axl.seed(3) >>> players = [axl.Defector(), axl.Random(), axl.Random()] - >>> amp = axl.ApproximateMoranProcess(players, cached_outcomes) + >>> amp = axl.ApproximateMoranProcess(players, cached_outcomes, seed=5) >>> results = amp.play() >>> amp.population_distribution() Counter({'Random: 0.5': 3}) -We see that, for this random seed, the :code:`Random: 0.5` won this Moran -process. This is not what happens in a standard Moran process where the +Note that by nature of being an approximation, it's possible that the results of an +`ApproximateMoranProcess` may not always match the results of a standard `MoranProcess`, +even for the same random seed. We see that, for this random seed, the :code:`Random: 0.5` +won this Moran process. This is not what happens in a standard Moran process where the :code:`Random: 0.5` player will not win:: - >>> axl.seed(3) - >>> amp = axl.MoranProcess(players) - >>> results = amp.play() - >>> amp.population_distribution() + >>> mp = axl.MoranProcess(players, seed=2) + >>> results = mp.play() + >>> mp.population_distribution() Counter({'Defector': 3}) diff --git a/docs/tutorials/further_topics/moran_processes_on_graphs.rst b/docs/tutorials/further_topics/moran_processes_on_graphs.rst index 7af9df0ba..2ca19181b 100644 --- a/docs/tutorials/further_topics/moran_processes_on_graphs.rst +++ b/docs/tutorials/further_topics/moran_processes_on_graphs.rst @@ -31,14 +31,13 @@ one graph is supplied to the process, the two graphs are assumed to be the same. To create a graph-based Moran process, use a graph as follows:: >>> from axelrod.graph import Graph - >>> axl.seed(40) >>> edges = [(0, 1), (1, 2), (2, 3), (3, 1)] >>> graph = Graph(edges) >>> players = [axl.Cooperator(), axl.Cooperator(), axl.Cooperator(), axl.Defector()] - >>> mp = axl.MoranProcess(players, interaction_graph=graph) + >>> mp = axl.MoranProcess(players, interaction_graph=graph, seed=40) >>> results = mp.play() >>> mp.population_distribution() - Counter({'Cooperator': 4}) + Counter({'Defector': 4}) You can supply the :code:`reproduction_graph` as a keyword argument. The standard Moran process is equivalent to using a complete graph with no loops diff --git a/docs/tutorials/further_topics/spatial_tournaments.rst b/docs/tutorials/further_topics/spatial_tournaments.rst index 3bb369ab2..db3c77d23 100644 --- a/docs/tutorials/further_topics/spatial_tournaments.rst +++ b/docs/tutorials/further_topics/spatial_tournaments.rst @@ -59,11 +59,10 @@ As anticipated not all players interact with each other. It is also possible to create a probabilistic ending spatial tournament:: - >>> prob_end_spatial_tournament = axl.Tournament(players, edges=edges, prob_end=.1, repetitions=1) - >>> axl.seed(0) + >>> prob_end_spatial_tournament = axl.Tournament(players, edges=edges, prob_end=.1, repetitions=1, seed=10) >>> prob_end_results = prob_end_spatial_tournament.play() We see that the match lengths are no longer all equal:: >>> prob_end_results.match_lengths - [[[0, 0, 18.0, 14.0], [0, 0, 6.0, 3.0], [18.0, 6.0, 0, 0], [14.0, 3.0, 0, 0]]] + [[[0, 0, 20.0, 1.0], [0, 0, 46.0, 13.0], [20.0, 46.0, 0, 0], [1.0, 13.0, 0, 0]]] diff --git a/docs/tutorials/getting_started/moran.rst b/docs/tutorials/getting_started/moran.rst index 16e3a22f2..26f34f58c 100644 --- a/docs/tutorials/getting_started/moran.rst +++ b/docs/tutorials/getting_started/moran.rst @@ -22,18 +22,17 @@ type. That type is declared the winner. To run an instance of the process with the library, proceed as follows:: >>> import axelrod as axl - >>> axl.seed(0) # for reproducible example >>> players = [axl.Cooperator(), axl.Defector(), ... axl.TitForTat(), axl.Grudger()] - >>> mp = axl.MoranProcess(players) + >>> mp = axl.MoranProcess(players, seed=1) >>> populations = mp.play() >>> mp.winning_strategy_name - 'Defector' + 'Tit For Tat' You can access some attributes of the process, such as the number of rounds:: >>> len(mp) - 16 + 15 The sequence of populations:: @@ -62,35 +61,33 @@ The scores in each round:: >>> for row in mp.score_history: ... print([round(element, 1) for element in row]) [6.0, 7.0, 7.0, 7.0] - [6.0, 7.0, 7.0, 7.0] - [6.0, 11.0, 7.0, 6.0] - [3.0, 11.0, 11.0, 3.0] - [6.0, 15.0, 6.0, 6.0] - [6.0, 15.0, 6.0, 6.0] - [3.0, 11.0, 11.0, 3.0] - [7.0, 7.0, 7.0, 0.0] - [7.0, 7.0, 7.0, 0.0] - [7.0, 7.0, 7.0, 0.0] - [7.0, 7.0, 7.0, 0.0] - [7.0, 7.0, 7.0, 0.0] - [7.0, 7.0, 7.0, 0.0] - [7.0, 7.0, 7.0, 0.0] - [7.0, 7.0, 7.0, 0.0] + [7.0, 3.1, 7.0, 7.0] + [7.0, 3.1, 7.0, 7.0] + [7.0, 3.1, 7.0, 7.0] + [7.0, 3.1, 7.0, 7.0] + [3.0, 3.0, 5.0, 5.0] + [3.0, 3.0, 5.0, 5.0] + [3.1, 7.0, 7.0, 7.0] + [3.1, 7.0, 7.0, 7.0] + [9.0, 9.0, 9.0, 9.0] + [9.0, 9.0, 9.0, 9.0] + [9.0, 9.0, 9.0, 9.0] + [9.0, 9.0, 9.0, 9.0] + [9.0, 9.0, 9.0, 9.0] We can plot the results of a Moran process with `mp.populations_plot()`. Let's use a larger population to get a bit more data:: >>> import random >>> import matplotlib.pyplot as plt - >>> axl.seed(15) # for reproducible example >>> players = [axl.Defector(), axl.Defector(), axl.Defector(), ... axl.Cooperator(), axl.Cooperator(), axl.Cooperator(), ... axl.TitForTat(), axl.TitForTat(), axl.TitForTat(), ... axl.Random()] - >>> mp = axl.MoranProcess(players=players, turns=200) + >>> mp = axl.MoranProcess(players=players, turns=200, seed=2) >>> populations = mp.play() >>> mp.winning_strategy_name - 'Cooperator' + 'Tit For Tat' >>> ax = mp.populations_plot() >>> plt.show() #doctest: +SKIP @@ -108,28 +105,26 @@ states, and will iterate forever. To prevent this, iterate with a loop (or function like :code:`takewhile` from :code:`itertools`):: >>> import axelrod as axl - >>> axl.seed(4) # for reproducible example >>> players = [axl.Cooperator(), axl.Defector(), ... axl.TitForTat(), axl.Grudger()] - >>> mp = axl.MoranProcess(players, mutation_rate=0.1) + >>> mp = axl.MoranProcess(players, mutation_rate=0.1, seed=10) >>> for _ in mp: ... if len(mp.population_distribution()) == 1: ... break >>> mp.population_distribution() - Counter({'Grudger': 4}) + Counter({'Defector': 4}) It is possible to pass a fitness function that scales the utility values. A common one used in the literature, [Ohtsuki2006]_, is :math:`f(s) = 1 - w + ws` where :math:`w` denotes the intensity of selection:: - >>> axl.seed(689) >>> players = (axl.Cooperator(), axl.Defector(), axl.Defector(), axl.Defector()) >>> w = 0.95 >>> fitness_transformation = lambda score: 1 - w + w * score - >>> mp = axl.MoranProcess(players, turns=10, fitness_transformation=fitness_transformation) + >>> mp = axl.MoranProcess(players, turns=10, fitness_transformation=fitness_transformation, seed=3) >>> populations = mp.play() >>> mp.winning_strategy_name - 'Cooperator' + 'Defector' Other types of implemented Moran processes: diff --git a/docs/tutorials/getting_started/running_axelrods_first_tournament.rst b/docs/tutorials/getting_started/running_axelrods_first_tournament.rst index 56b436b30..56f67c5cf 100644 --- a/docs/tutorials/getting_started/running_axelrods_first_tournament.rst +++ b/docs/tutorials/getting_started/running_axelrods_first_tournament.rst @@ -37,14 +37,14 @@ Creating the tournament ----------------------- Now we create and run the tournament, we will set a seed to ensure -reproducibility and 50 repetitions to smooth the random effects. We use 5 +reproducibility and 5 repetitions to smooth the random effects. We use 5 repetitions as this is what was done in [Axelrod1980]_:: - >>> axl.seed(0) >>> tournament = axl.Tournament( ... players=first_tournament_participants_ordered_by_reported_rank, ... turns=200, - ... repetitions=5 + ... repetitions=5, + ... seed=1, ... ) >>> results = tournament.play() @@ -61,14 +61,14 @@ The results object contains the ranked names:: Tit For Tat First by Tideman and Chieruzzi: (D, D) First by Nydegger - First by Davis: 10 Grudger + First by Davis: 10 First by Graaskamp: 0.05 First by Downing First by Feld: 1.0, 0.5, 200 - First by Joss: 0.9 - First by Tullock Random: 0.5 + First by Tullock + First by Joss: 0.9 First by Anonymous We see that `TitForTat` does not in fact win this tournament. @@ -116,7 +116,7 @@ The first 6 strategies have similar scores which could indicate that the original work by Axelrod was not run with sufficient repetitions. Another explanation is that all the strategies are implemented from the descriptions given in [Axelrod1980]_ and there is no source code to base this on. This leads -to some strategies being ambigious. These are all clearly explained in the +to some strategies being ambiguous. These are all clearly explained in the strategy docstrings. For example:: >>> print(axl.FirstByAnonymous.__doc__) @@ -147,13 +147,13 @@ Other outcomes -------------- If we run the tournament with other seeds, the results are different. For -example, with `130` Tit For Tat wins:: +example, with `796` Tit For Tat wins:: - >>> axl.seed(130) >>> tournament = axl.Tournament( ... players=first_tournament_participants_ordered_by_reported_rank, ... turns=200, - ... repetitions=5 + ... repetitions=5, + ... seed=796, ... ) >>> results = tournament.play() >>> for name in results.ranked_names: @@ -162,37 +162,38 @@ example, with `130` Tit For Tat wins:: First by Stein and Rapoport: 0.05: (D, D) First by Grofman First by Shubik - First by Nydegger First by Tideman and Chieruzzi: (D, D) - First by Davis: 10 + First by Nydegger Grudger + First by Davis: 10 First by Graaskamp: 0.05 First by Downing First by Feld: 1.0, 0.5, 200 First by Joss: 0.9 First by Tullock - Random: 0.5 First by Anonymous + Random: 0.5 -With `1238` the strategy submitted by Shubik wins:: - >>> axl.seed(1238) +With `208` the strategy submitted by Grofman wins:: + >>> tournament = axl.Tournament( ... players=first_tournament_participants_ordered_by_reported_rank, ... turns=200, - ... repetitions=5 + ... repetitions=5, + ... seed=208 ... ) >>> results = tournament.play() >>> for name in results.ranked_names: ... print(name) - First by Shubik - First by Stein and Rapoport: 0.05: (D, D) First by Grofman + First by Stein and Rapoport: 0.05: (D, D) Tit For Tat - First by Nydegger + First by Shubik First by Tideman and Chieruzzi: (D, D) - Grudger + First by Nydegger First by Davis: 10 + Grudger First by Graaskamp: 0.05 First by Downing First by Feld: 1.0, 0.5, 200 @@ -200,3 +201,5 @@ With `1238` the strategy submitted by Shubik wins:: First by Joss: 0.9 First by Anonymous Random: 0.5 + + From 1363ea5e7f5ef2aec2f6d12c51c7700df3c72dad Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sat, 4 Jul 2020 14:42:56 -0700 Subject: [PATCH 049/121] Fix remaining doctests --- README.rst | 3 +-- axelrod/fingerprint.py | 4 ++++ docs/tutorials/advanced/setting_a_seed.rst | 4 ++++ docs/tutorials/further_topics/fingerprinting.rst | 14 +++++--------- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/README.rst b/README.rst index 79fa83d72..bc430b60d 100644 --- a/README.rst +++ b/README.rst @@ -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'] diff --git a/axelrod/fingerprint.py b/axelrod/fingerprint.py index 68706fb6a..9baad139c 100644 --- a/axelrod/fingerprint.py +++ b/axelrod/fingerprint.py @@ -303,6 +303,8 @@ def fingerprint( if None, will auto-generate a filename. progress_bar : bool Whether or not to create a progress bar which will be updated + seed : int, optional + Random seed for reproducibility Returns ---------- @@ -430,6 +432,7 @@ def fingerprint( processes: int = None, filename: str = None, progress_bar: bool = True, + seed: int = None ) -> np.array: """Creates a spatial tournament to run the necessary matches to obtain fingerprint data. @@ -476,6 +479,7 @@ def fingerprint( turns=turns, noise=noise, repetitions=repetitions, + seed=seed ) tournament.play( filename=filename, diff --git a/docs/tutorials/advanced/setting_a_seed.rst b/docs/tutorials/advanced/setting_a_seed.rst index 9f4b168d7..cd3791296 100644 --- a/docs/tutorials/advanced/setting_a_seed.rst +++ b/docs/tutorials/advanced/setting_a_seed.rst @@ -75,3 +75,7 @@ Moran Process Similarly, a Moran process is essentially another type of tournament. The library's implementation will propagate child seeds to each match to ensure reproducibility. + + +Fingerprints +------------ diff --git a/docs/tutorials/further_topics/fingerprinting.rst b/docs/tutorials/further_topics/fingerprinting.rst index 1f0845d7e..8493b4915 100644 --- a/docs/tutorials/further_topics/fingerprinting.rst +++ b/docs/tutorials/further_topics/fingerprinting.rst @@ -23,11 +23,10 @@ Here is how to create a fingerprint of :code:`WinStayLoseShift` using :code:`TitForTat` as a probe:: >>> import axelrod as axl - >>> axl.seed(0) # Fingerprinting is a random process >>> strategy = axl.WinStayLoseShift >>> probe = axl.TitForTat >>> af = axl.AshlockFingerprint(strategy, probe) - >>> data = af.fingerprint(turns=10, repetitions=2, step=0.2) + >>> data = af.fingerprint(turns=10, repetitions=2, step=0.2, seed=1) >>> data {... >>> data[(0, 0)] @@ -70,15 +69,14 @@ Note that it is also possible to pass a player instance to be fingerprinted and/or as a probe. This allows for the fingerprinting of parametrized strategies:: - >>> axl.seed(0) >>> player = axl.Random(p=.1) >>> probe = axl.GTFT(p=.9) >>> af = axl.AshlockFingerprint(player, probe) - >>> data = af.fingerprint(turns=10, repetitions=2, step=0.2) + >>> data = af.fingerprint(turns=10, repetitions=2, step=0.2, seed=2) >>> data {... >>> data[(0, 0)] - 4.4... + 4.1 Transitive Fingerprint ----------------------- @@ -91,10 +89,9 @@ By default the set of opponents consists of :code:`50` Random players that cooperate with increasing probability. This is how to obtain the transitive fingerprint for :code:`TitForTat`:: - >>> axl.seed(0) >>> player = axl.TitForTat() >>> tf = axl.TransitiveFingerprint(player) - >>> data = tf.fingerprint(turns=40) + >>> data = tf.fingerprint(turns=40, seed=3) The data produced is a :code:`numpy` array showing the cooperation rate against a given opponent (row) in a given turn (column):: @@ -113,10 +110,9 @@ It is also possible to visualise the fingerprint:: It is also possible to fingerprint against a given set of opponents:: - >>> axl.seed(1) >>> opponents = [s() for s in axl.demo_strategies] >>> tf = axl.TransitiveFingerprint(player, opponents=opponents) - >>> data = tf.fingerprint(turns=5, repetitions=10) + >>> data = tf.fingerprint(turns=5, repetitions=10, seed=4) The name of the opponents can be displayed in the plot:: From ca781194dee36a8b0d08b85402033bf8b34faec6 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sat, 4 Jul 2020 17:23:58 -0700 Subject: [PATCH 050/121] Make EvolvablePlayers properly seed reproducible --- axelrod/evolvable_player.py | 21 ++++++++- axelrod/player.py | 5 +-- axelrod/strategies/ann.py | 5 +-- axelrod/strategies/cycler.py | 2 +- axelrod/strategies/finite_state_machines.py | 7 +-- axelrod/strategies/gambler.py | 4 +- axelrod/strategies/hmm.py | 5 +-- axelrod/strategies/lookerup.py | 5 +-- axelrod/tests/strategies/test_ann.py | 8 ++-- axelrod/tests/strategies/test_cycler.py | 23 +++++----- .../tests/strategies/test_evolvable_player.py | 45 ++++++++++++------- .../strategies/test_finite_state_machines.py | 13 +++--- axelrod/tests/strategies/test_gambler.py | 6 +-- axelrod/tests/strategies/test_hmm.py | 14 +++--- axelrod/tests/strategies/test_lookerup.py | 5 ++- axelrod/tests/unit/test_moran.py | 6 +-- docs/tutorials/advanced/setting_a_seed.rst | 5 ++- .../further_topics/evolvable_players.rst | 10 ++++- 18 files changed, 121 insertions(+), 68 deletions(-) diff --git a/axelrod/evolvable_player.py b/axelrod/evolvable_player.py index bfc6da147..20f835828 100644 --- a/axelrod/evolvable_player.py +++ b/axelrod/evolvable_player.py @@ -11,6 +11,18 @@ def __init__(self, *args): super().__init__(*args) +class SeedNotGivenError(Exception): + """Error indicating that a required seed was not supplied.""" + def __init__(self, *args): + super().__init__(*args) + + +# class SeedNullingError(Exception): +# """Error indicating that a required seed was not supplied.""" +# def __init__(self, *args): +# super().__init__(*args) + + class EvolvablePlayer(Player): """A class for a player that can evolve, for use in the Moran process or with reinforcement learning algorithms. @@ -21,9 +33,16 @@ class EvolvablePlayer(Player): parent_class = Player parent_kwargs = [] # type: List[str] + # Parameter seed is actually required. def __init__(self, seed=None): + # if seed is None: + # raise SeedNotGivenError(seed) + # # Was the seed already set? + # if (not hasattr(self, "_seed")) or (hasattr(self, "_seed") and self._seed==None): + # raise SeedNotGivenError() + # raise SeedNullingError() + # else: super().__init__() - # if set_seed: self.set_seed(seed=seed) def overwrite_init_kwargs(self, **kwargs): diff --git a/axelrod/player.py b/axelrod/player.py index 0ac55d05d..e9466a0dd 100644 --- a/axelrod/player.py +++ b/axelrod/player.py @@ -54,7 +54,6 @@ def __init__(self): self._history = History() self.classifier = copy.deepcopy(self.classifier) self.set_match_attributes() - # self.set_seed(seed=self._seed) def __eq__(self, other): """ @@ -118,13 +117,14 @@ def set_match_attributes(self, length=-1, game=None, noise=0): self.match_attributes = {"length": length, "game": game, "noise": noise} self.receive_match_attributes() - def set_seed(self, seed=None): + def set_seed(self, seed): """Set a random seed for the player's random number generator.""" if seed is None: warnings.warn( "Initializing player with seed from Axelrod module random number generator." " Results may not be seed reproducible.") self._seed = _module_random.random_seed_int() + # raise Exception() else: self._seed = seed self._random = RandomGenerator(seed=self._seed) @@ -164,7 +164,6 @@ def clone(self): cls = self.__class__ new_player = cls(**self.init_kwargs) new_player.match_attributes = copy.copy(self.match_attributes) - # new_player.set_seed(self._seed) return new_player def reset(self): diff --git a/axelrod/strategies/ann.py b/axelrod/strategies/ann.py index e4d0bfcc3..136de36bb 100644 --- a/axelrod/strategies/ann.py +++ b/axelrod/strategies/ann.py @@ -197,7 +197,7 @@ def __init__( self, num_features: int, num_hidden: int, weights: List[float] = None ) -> None: - super().__init__() + Player.__init__(self) self.num_features = num_features self.num_hidden = num_hidden self._process_weights(weights, num_features, num_hidden) @@ -234,14 +234,13 @@ def __init__( mutation_distance: int = 5, seed: int = None ) -> None: - self.set_seed(seed=seed) + EvolvablePlayer.__init__(self, seed=seed) num_features, num_hidden, weights, mutation_probability = self._normalize_parameters( num_features, num_hidden, weights, mutation_probability) ANN.__init__(self, num_features=num_features, num_hidden=num_hidden, weights=weights) - EvolvablePlayer.__init__(self) self.mutation_probability = mutation_probability self.mutation_distance = mutation_distance self.overwrite_init_kwargs( diff --git a/axelrod/strategies/cycler.py b/axelrod/strategies/cycler.py index 98783e7a4..dd8e9b8ce 100644 --- a/axelrod/strategies/cycler.py +++ b/axelrod/strategies/cycler.py @@ -88,7 +88,7 @@ def __init__(self, cycle: str = "CCD") -> None: Alternator is equivalent to Cycler("CD") """ - super().__init__() + Player.__init__(self) self.cycle = cycle self.set_cycle(cycle=cycle) diff --git a/axelrod/strategies/finite_state_machines.py b/axelrod/strategies/finite_state_machines.py index 231f4a70b..015f9a0d1 100644 --- a/axelrod/strategies/finite_state_machines.py +++ b/axelrod/strategies/finite_state_machines.py @@ -115,7 +115,8 @@ def __init__( initial_state: int = 1, initial_action: Action = C ) -> None: - super().__init__() + # super().__init__() + Player.__init__(self) self.initial_state = initial_state self.initial_action = initial_action self.fsm = SimpleFSM(transitions, initial_state) @@ -153,7 +154,7 @@ def __init__( ) -> None: """If transitions, initial_state, and initial_action are None then generate random parameters using num_states.""" - self.set_seed(seed=seed) + EvolvablePlayer.__init__(self, seed=seed) transitions, initial_state, initial_action, num_states = self._normalize_parameters( transitions, initial_state, initial_action, num_states) FSMPlayer.__init__( @@ -161,7 +162,7 @@ def __init__( transitions=transitions, initial_state=initial_state, initial_action=initial_action) - EvolvablePlayer.__init__(self) + # EvolvablePlayer.__init__(self) self.mutation_probability = mutation_probability self.overwrite_init_kwargs( transitions=transitions, diff --git a/axelrod/strategies/gambler.py b/axelrod/strategies/gambler.py index bb00141a7..dbaac5aca 100644 --- a/axelrod/strategies/gambler.py +++ b/axelrod/strategies/gambler.py @@ -68,9 +68,9 @@ def __init__( initial_actions=initial_actions, pattern=pattern, parameters=parameters, - mutation_probability=mutation_probability + mutation_probability=mutation_probability, + seed=seed ) - self.set_seed(seed=seed) self.pattern = list(self.pattern) Gambler.__init__( self, diff --git a/axelrod/strategies/hmm.py b/axelrod/strategies/hmm.py index 4f425f36c..b43bba6e0 100644 --- a/axelrod/strategies/hmm.py +++ b/axelrod/strategies/hmm.py @@ -149,7 +149,7 @@ def __init__( initial_state=0, initial_action=C ) -> None: - super().__init__() + Player.__init__(self) if not transitions_C: transitions_C = [[1]] transitions_D = [[1]] @@ -211,7 +211,7 @@ def __init__( mutation_probability=None, seed: int = None ) -> None: - self.set_seed(seed=seed) + EvolvablePlayer.__init__(self, seed=seed) transitions_C, transitions_D, emission_probabilities, initial_state, initial_action, num_states, mutation_probability = self._normalize_parameters( transitions_C, transitions_D, emission_probabilities, initial_state, initial_action, num_states, mutation_probability) self.mutation_probability = mutation_probability @@ -222,7 +222,6 @@ def __init__( initial_state=initial_state, initial_action=initial_action) self.hmm._random = self._random - EvolvablePlayer.__init__(self) self.overwrite_init_kwargs( transitions_C=transitions_C, transitions_D=transitions_D, diff --git a/axelrod/strategies/lookerup.py b/axelrod/strategies/lookerup.py index 176398112..8fd6f80d8 100644 --- a/axelrod/strategies/lookerup.py +++ b/axelrod/strategies/lookerup.py @@ -322,7 +322,7 @@ def __init__( parameters: Plays = None ) -> None: - super().__init__() + Player.__init__(self) self.parameters = parameters self.pattern = pattern self._lookup = self._get_lookup_table(lookup_dict, pattern, parameters) @@ -407,7 +407,7 @@ def __init__( mutation_probability: float = None, seed: int = None ) -> None: - self.set_seed(seed=seed) + EvolvablePlayer.__init__(self, seed=seed) lookup_dict, initial_actions, pattern, parameters, mutation_probability = self._normalize_parameters( lookup_dict, initial_actions, pattern, parameters, mutation_probability ) @@ -418,7 +418,6 @@ def __init__( pattern=pattern, parameters=parameters, ) - EvolvablePlayer.__init__(self) self.mutation_probability = mutation_probability self.overwrite_init_kwargs( lookup_dict=lookup_dict, diff --git a/axelrod/tests/strategies/test_ann.py b/axelrod/tests/strategies/test_ann.py index 5c7c9235d..985462e34 100644 --- a/axelrod/tests/strategies/test_ann.py +++ b/axelrod/tests/strategies/test_ann.py @@ -103,12 +103,14 @@ class TestEvolvableANN(unittest.TestCase): def test_normalized_parameters(self): self.assertRaises( InsufficientParametersError, - self.player_class(3, 3)._normalize_parameters, + self.player_class(3, 3, seed=1)._normalize_parameters, + # To prevent exception from unset seed. ) self.assertRaises( InsufficientParametersError, - self.player_class(3, 3)._normalize_parameters, - weights=nn_weights["Evolved ANN 5"][2] + self.player_class(3, 3, seed=1)._normalize_parameters, + weights=nn_weights["Evolved ANN 5"][2], + # To prevent exception from unset seed. ) diff --git a/axelrod/tests/strategies/test_cycler.py b/axelrod/tests/strategies/test_cycler.py index 5b14642e8..436a464a5 100644 --- a/axelrod/tests/strategies/test_cycler.py +++ b/axelrod/tests/strategies/test_cycler.py @@ -156,25 +156,28 @@ def test_normalized_parameters(self): # Must specify at least one of cycle or cycle_length self.assertRaises( InsufficientParametersError, - self.player_class + self.player_class, + seed=1, # to prevent warning for unset seed ) self.assertRaises( InsufficientParametersError, self.player_class, - cycle="" + cycle="", + seed=1, # to prevent warning for unset seed ) self.assertRaises( InsufficientParametersError, self.player_class, - cycle_length=0 + cycle_length=0, + seed=1, # to prevent warning for unset seed ) cycle = "C" * random.randint(0, 20) + "D" * random.randint(0, 20) - self.assertEqual(self.player_class(cycle=cycle)._normalize_parameters(cycle=cycle), + self.assertEqual(self.player_class(cycle=cycle, seed=1)._normalize_parameters(cycle=cycle), (cycle, len(cycle))) cycle_length = random.randint(1, 20) - random_cycle, cycle_length2 = self.player_class(cycle=cycle)._normalize_parameters(cycle_length=cycle_length) + random_cycle, cycle_length2 = self.player_class(cycle=cycle, seed=1)._normalize_parameters(cycle_length=cycle_length) self.assertEqual(len(random_cycle), cycle_length) self.assertEqual(cycle_length, cycle_length2) @@ -183,18 +186,18 @@ def test_crossover_even_length(self): cycle2 = "D" * 6 cross_cycle = "CCCCCD" - player1 = self.player_class(cycle=cycle1, seed=4) - player2 = self.player_class(cycle=cycle2, seed=5) + player1 = self.player_class(cycle=cycle1, seed=1) + player2 = self.player_class(cycle=cycle2, seed=2) crossed = player1.crossover(player2) self.assertEqual(cross_cycle, crossed.cycle) def test_crossover_odd_length(self): cycle1 = "C" * 7 cycle2 = "D" * 7 - cross_cycle = "CCDDDDD" + cross_cycle = "CCCCCDD" - player1 = self.player_class(cycle=cycle1, seed=6) - player2 = self.player_class(cycle=cycle2, seed=7) + player1 = self.player_class(cycle=cycle1, seed=1) + player2 = self.player_class(cycle=cycle2, seed=2) crossed = player1.crossover(player2) self.assertEqual(cross_cycle, crossed.cycle) diff --git a/axelrod/tests/strategies/test_evolvable_player.py b/axelrod/tests/strategies/test_evolvable_player.py index a0ef10658..40a39cc8a 100644 --- a/axelrod/tests/strategies/test_evolvable_player.py +++ b/axelrod/tests/strategies/test_evolvable_player.py @@ -12,6 +12,14 @@ def PartialClass(cls, **kwargs): class PartialedClass(cls): + # Set a seed to avoid undefined behavior in tests. + try: + seed = kwargs["seed"] + except KeyError: + kwargs["seed"] = 1 + else: + if seed is None: + kwargs["seed"] = 1 __init__ = functools.partialmethod( cls.__init__, **kwargs) @@ -21,7 +29,7 @@ class PartialedClass(cls): class EvolvableTestOpponent(axl.EvolvablePlayer): name = "EvolvableTestOpponent" - def __init__(self, value=None, seed=None): + def __init__(self, value=None, seed=1): super().__init__(seed=seed) if value: self.value = value @@ -51,9 +59,12 @@ class TestEvolvablePlayer(TestPlayer): parent_class = None init_parameters = dict() - def player(self, seed=None): + def player(self, seed=1): + if seed is None: + raise Exception() params = self.init_parameters.copy() - params["seed"] = seed + if "seed" not in params: + params["seed"] = seed return self.player_class(**params) def test_repr(self): @@ -74,8 +85,8 @@ def test_randomization(self): """Test that randomization on initialization produces different strategies.""" if self.init_parameters: return - player1 = self.player(seed=0) - player2 = self.player(seed=0) + player1 = self.player(seed=1) + player2 = self.player(seed=1) self.assertEqual(player1, player2) for seed_ in range(2, 20): @@ -100,14 +111,14 @@ def test_mutate_variations(self): def test_mutate_and_clone(self): """Test that mutated players clone properly.""" - player = self.player(seed=0) + player = self.player(seed=1) mutant = player.clone().mutate() clone = mutant.clone() self.assertEqual(clone, mutant) def test_crossover(self): """Test that crossover produces different strategies.""" - rng = axl.RandomGenerator(seed=0) + rng = axl.RandomGenerator(seed=1) for _ in range(20): players = [] for _ in range(2): @@ -130,7 +141,7 @@ def test_crossover_mismatch(self): def test_serialization(self): """Serializing and deserializing should return the original player.""" - player = self.player(seed=0) + player = self.player(seed=1) serialized = player.serialize_parameters() deserialized_player = player.__class__.deserialize_parameters(serialized) self.assertEqual(player, deserialized_player) @@ -138,7 +149,7 @@ def test_serialization(self): def test_serialization_csv(self): """Serializing and deserializing should return the original player.""" - player = self.player(seed=0) + player = self.player(seed=1) serialized = player.serialize_parameters() s = "0, 1, {}, 3".format(serialized) s2 = s.split(',')[2] @@ -146,15 +157,15 @@ def test_serialization_csv(self): self.assertEqual(player, deserialized_player) self.assertEqual(deserialized_player, deserialized_player.clone()) - def behavior_test(self, player1, player2): + def behavior_test(self, player1, player2, seed=7): """Test that the evolvable player plays the same as its (nonevolvable) parent class.""" for opponent_class in [axl.Random, axl.TitForTat, axl.Alternator]: opponent = opponent_class() - match = axl.Match((player1.clone(), opponent), seed=7) + match = axl.Match((player1.clone(), opponent), seed=seed) results1 = match.play() opponent = opponent_class() - match = axl.Match((player2.clone(), opponent), seed=7) + match = axl.Match((player2.clone(), opponent), seed=seed) results2 = match.play() self.assertEqual(results1, results2) @@ -164,7 +175,7 @@ def test_behavior(self): if not self.parent_class: return - player = self.player_class(**self.init_parameters) + player = self.player() init_kwargs = {k: player.init_kwargs[k] for k in self.parent_kwargs} parent_player = self.parent_class(**init_kwargs) self.behavior_test(player, parent_player) @@ -185,11 +196,11 @@ def test_crossover_lists(self): list1 = [[0, C, 1, D], [0, D, 0, D], [1, C, 1, C], [1, D, 1, D]] list2 = [[0, D, 1, C], [0, C, 0, C], [1, D, 1, D], [1, C, 1, C]] - rng = axl.RandomGenerator(seed=100) + rng = axl.RandomGenerator(seed=5) crossed = crossover_lists(list1, list2, rng) self.assertEqual(crossed, list1[:3] + list2[3:]) - rng = axl.RandomGenerator(seed=101) + rng = axl.RandomGenerator(seed=1) crossed = crossover_lists(list1, list2, rng) self.assertEqual(crossed, list1[:1] + list2[1:]) @@ -197,10 +208,10 @@ def test_crossover_dictionaries(self): dict1 = {'1': 1, '2': 2, '3': 3} dict2 = {'1': 'a', '2': 'b', '3': 'c'} - rng = axl.RandomGenerator(seed=100) + rng = axl.RandomGenerator(seed=1) crossed = crossover_dictionaries(dict1, dict2, rng) self.assertEqual(crossed, {'1': 1, '2': 'b', '3': 'c'}) - rng = axl.RandomGenerator(seed=101) + rng = axl.RandomGenerator(seed=2) crossed = crossover_dictionaries(dict1, dict2, rng) self.assertEqual(crossed, dict2) diff --git a/axelrod/tests/strategies/test_finite_state_machines.py b/axelrod/tests/strategies/test_finite_state_machines.py index 967647874..2ad48ed8b 100644 --- a/axelrod/tests/strategies/test_finite_state_machines.py +++ b/axelrod/tests/strategies/test_finite_state_machines.py @@ -1052,12 +1052,14 @@ class TestEvolvableFSMPlayer(unittest.TestCase): def test_normalized_parameters(self): self.assertRaises( InsufficientParametersError, - self.player_class + self.player_class, + seed=1, # To prevent exception from unset seed. ) self.assertRaises( InsufficientParametersError, self.player_class, - transitions=[[0, C, 1, D], [0, D, 0, D], [1, C, 1, C], [1, D, 1, D]] + transitions=[[0, C, 1, D], [0, D, 0, D], [1, C, 1, C], [1, D, 1, D]], + seed=1, # To prevent exception from unset seed. ) def test_init(self): @@ -1065,7 +1067,8 @@ def test_init(self): player = axl.EvolvableFSMPlayer( transitions=transitions, initial_action=D, - initial_state=1 + initial_state=1, + seed=1, # To prevent exception from unset seed. ) self.assertEqual(player.num_states, 2) self.assertEqual(player.fsm.transitions(), transitions) @@ -1075,7 +1078,7 @@ def test_init(self): def test_vector_to_instance(self): num_states = 4 vector = [random.random() for _ in range(num_states * 4 + 1)] - player = axl.EvolvableFSMPlayer(num_states=num_states) + player = axl.EvolvableFSMPlayer(num_states=num_states, seed=1) player.receive_vector(vector) self.assertIsInstance(player, axl.EvolvableFSMPlayer) @@ -1086,7 +1089,7 @@ def test_vector_to_instance(self): def test_create_vector_bounds(self): num_states = 4 - player = axl.EvolvableFSMPlayer(num_states=num_states) + player = axl.EvolvableFSMPlayer(num_states=num_states, seed=1) lb, ub = player.create_vector_bounds() self.assertEqual(lb, [0] * (4 * num_states + 1)) self.assertEqual(ub, [1] * (4 * num_states + 1)) diff --git a/axelrod/tests/strategies/test_gambler.py b/axelrod/tests/strategies/test_gambler.py index db645de95..cc58083e3 100755 --- a/axelrod/tests/strategies/test_gambler.py +++ b/axelrod/tests/strategies/test_gambler.py @@ -524,7 +524,7 @@ class TestEvolvableGambler(unittest.TestCase): def test_receive_vector(self): plays, op_plays, op_start_plays = 1, 1, 1 player = axl.EvolvableGambler( - parameters=(plays, op_plays, op_start_plays)) + parameters=(plays, op_plays, op_start_plays), seed=1) self.assertRaises(AttributeError, axl.EvolvableGambler.__getattribute__, *[player, 'vector']) @@ -536,7 +536,7 @@ def test_receive_vector(self): def test_vector_to_instance(self): plays, op_plays, op_start_plays = 1, 1, 1 player = axl.EvolvableGambler( - parameters=(plays, op_plays, op_start_plays)) + parameters=(plays, op_plays, op_start_plays), seed=1) vector = [random.random() for _ in range(8)] player.receive_vector(vector) @@ -548,7 +548,7 @@ def test_vector_to_instance(self): def test_create_vector_bounds(self): plays, op_plays, op_start_plays = 1, 1, 1 player = axl.EvolvableGambler( - parameters=(plays, op_plays, op_start_plays)) + parameters=(plays, op_plays, op_start_plays), seed=1) lb, ub = player.create_vector_bounds() self.assertIsInstance(lb, list) self.assertIsInstance(ub, list) diff --git a/axelrod/tests/strategies/test_hmm.py b/axelrod/tests/strategies/test_hmm.py index a5df3f9fe..07aef7270 100644 --- a/axelrod/tests/strategies/test_hmm.py +++ b/axelrod/tests/strategies/test_hmm.py @@ -220,7 +220,8 @@ def test_normalized_parameters(self): self.assertRaises( InsufficientParametersError, - self.player_class + self.player_class, + seed=1 ) self.assertRaises( InsufficientParametersError, @@ -228,7 +229,8 @@ def test_normalized_parameters(self): transitions_C=transitions_C, transitions_D=transitions_D, emission_probabilities=emission_probabilities, - initial_state=None + initial_state=None, + seed=1 ) self.assertRaises( InsufficientParametersError, @@ -236,13 +238,15 @@ def test_normalized_parameters(self): transitions_C=transitions_C, transitions_D=transitions_D, emission_probabilities=emission_probabilities, - initial_action=None + initial_action=None, + seed=1 ) self.assertRaises( InsufficientParametersError, self.player_class, initial_state=initial_state, initial_action=initial_action, + seed=1 ) def test_vector_to_instance(self): @@ -253,7 +257,7 @@ def test_vector_to_instance(self): vector.extend(list(rng.random_vector(num_states))) for _ in range(num_states + 1): vector.append(rng.random()) - player = self.player_class(num_states=num_states) + player = self.player_class(num_states=num_states, seed=1) player.receive_vector(vector=vector) self.assertIsInstance(player, self.player_class) @@ -261,7 +265,7 @@ def test_create_vector_bounds(self): num_states = 4 size = 2 * num_states ** 2 + num_states + 1 - player = self.player_class(num_states=num_states) + player = self.player_class(num_states=num_states, seed=1) lb, ub = player.create_vector_bounds() self.assertIsInstance(lb, list) diff --git a/axelrod/tests/strategies/test_lookerup.py b/axelrod/tests/strategies/test_lookerup.py index 92cb8f7b4..c48474388 100755 --- a/axelrod/tests/strategies/test_lookerup.py +++ b/axelrod/tests/strategies/test_lookerup.py @@ -666,18 +666,21 @@ def test_normalized_parameters(self): self.assertRaises( InsufficientParametersError, - self.player_class + self.player_class, + seed=1 ) self.assertRaises( InsufficientParametersError, self.player_class, pattern=pattern, initial_actions=initial_actions, + seed=1 ) self.assertRaises( InsufficientParametersError, self.player_class, lookup_dict=lookup_dict, + seed=1 ) diff --git a/axelrod/tests/unit/test_moran.py b/axelrod/tests/unit/test_moran.py index 8e05b53bc..b9ea897bf 100644 --- a/axelrod/tests/unit/test_moran.py +++ b/axelrod/tests/unit/test_moran.py @@ -439,7 +439,7 @@ def test_complete(self): def test_cycle(self): """A cycle should sometimes produce different results vs. the default case.""" - seeds = [(1, True), (8, False)] + seeds = [(1, True), (10, False)] players = [] N = 6 graph = axl.graph.cycle(N) @@ -459,7 +459,7 @@ def test_cycle(self): def test_asymmetry(self): """Asymmetry in interaction and reproduction should sometimes produce different results.""" - seeds = [(1, True), (21, False)] + seeds = [(1, True), (20, False)] players = [] N = 6 graph1 = axl.graph.cycle(N) @@ -486,7 +486,7 @@ def test_asymmetry(self): def test_cycle_death_birth(self): """Test that death-birth can have different outcomes in the graph case.""" - seeds = [(1, True), (5, False)] + seeds = [(1, True), (6, False)] players = [] N = 6 graph = axl.graph.cycle(N) diff --git a/docs/tutorials/advanced/setting_a_seed.rst b/docs/tutorials/advanced/setting_a_seed.rst index cd3791296..cf3b16b45 100644 --- a/docs/tutorials/advanced/setting_a_seed.rst +++ b/docs/tutorials/advanced/setting_a_seed.rst @@ -74,8 +74,11 @@ Moran Process ------------- Similarly, a Moran process is essentially another type of tournament. The library's implementation -will propagate child seeds to each match to ensure reproducibility. +will propagate child seeds to each match to ensure reproducibility. See also the documentation on +:code:`EvolvablePlayers`. Fingerprints ------------ +Since fingerprint generation depends on tournaments, fingerprints can also be given a seed for +reproducibility. diff --git a/docs/tutorials/further_topics/evolvable_players.rst b/docs/tutorials/further_topics/evolvable_players.rst index 0f40d32aa..799a745b6 100644 --- a/docs/tutorials/further_topics/evolvable_players.rst +++ b/docs/tutorials/further_topics/evolvable_players.rst @@ -26,8 +26,16 @@ of the Moran process:: >>> import axelrod as axl >>> C = axl.Action.C >>> players = [axl.EvolvableFSMPlayer(num_states=2, initial_state=1, initial_action=C) for _ in range(5)] - >>> mp = axl.MoranProcess(players, turns=10, mutation_method="atomic") + >>> mp = axl.MoranProcess(players, turns=10, mutation_method="atomic", seed=1) >>> population = mp.play() # doctest: +SKIP Note that this may cause the Moran process to fail to converge, if the mutation rates are very high or the population size very large. See :ref:`moran-process` for more information. + +Reproducible Seeding +-------------------- + +:code:`EvolvablePlayers` are inherently stochastic. For reproducibility of results, they can be seeded. When +using the Moran process, a process level seed is sufficient. Child seeds will be created and propagated +in a reproducible way. If initialized without a seed, an :code:`EvolvablePlayer` will be given a +random seed in a non-reproducible way. From fef4a4070eb702a3bc11263cbe13edaed31ae41e Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sun, 5 Jul 2020 21:00:29 -0700 Subject: [PATCH 051/121] Rework test_strategy_transformers and update seeds --- .../tests/unit/test_strategy_transformers.py | 709 +++++++++--------- 1 file changed, 345 insertions(+), 364 deletions(-) diff --git a/axelrod/tests/unit/test_strategy_transformers.py b/axelrod/tests/unit/test_strategy_transformers.py index 5d56d004d..808d7a694 100644 --- a/axelrod/tests/unit/test_strategy_transformers.py +++ b/axelrod/tests/unit/test_strategy_transformers.py @@ -1,9 +1,8 @@ -import unittest - import axelrod as axl from axelrod.strategy_transformers import * from axelrod.tests.strategies.test_cooperator import TestCooperator from axelrod.tests.strategies.test_titfortat import TestTitForTat +from axelrod.tests.strategies.test_player import TestMatch C, D = axl.Action.C, axl.Action.D @@ -18,7 +17,8 @@ class CanNotPickle(axl.Cooperator): pass -class TestTransformers(unittest.TestCase): +class TestTransformers(TestMatch): + """Test generic transformer properties.""" def test_player_can_be_pickled(self): player = axl.Cooperator() self.assertTrue(player_can_be_pickled(player)) @@ -121,16 +121,6 @@ def test_StrategyReBuilder_many_decorators(self): new_player.__dict__.update(update_dict) self.assertEqual(player, new_player) - def test_all_strategies(self): - # Attempt to transform each strategy to ensure that implementation - # choices (like use of super) do not cause issues - opponent = axl.Cooperator() - for s in axl.strategies: - opponent = axl.Cooperator() - player = IdentityTransformer()(s)() - match = axl.Match((player, opponent), turns=3) - match.play() - def test_naming(self): """Tests that the player and class names are properly modified.""" cls = FlipTransformer()(axl.Cooperator) @@ -182,24 +172,132 @@ def test_cloning(self): results = match.play() self.assertEqual(results, [(C, D), (C, D)]) - def test_generic(self): - """Test that the generic wrapper does nothing.""" - # This is the identity transformer - transformer = StrategyTransformerFactory(generic_strategy_wrapper)() - Cooperator2 = transformer(axl.Cooperator) - p1 = Cooperator2() + def test_composition(self): + """Tests explicitly that transformations can be chained or composed.""" + cls1 = InitialTransformer([D, D])(axl.Cooperator) + cls2 = FinalTransformer([D, D])(cls1) + p1 = cls2() p2 = axl.Cooperator() - match = axl.Match((p1, p2), turns=2) - results = match.play() - self.assertEqual(results, [(C, C), (C, C)]) + self.versus_test(p1, p2, [D, D, C, C, C, C, D, D], [C] * 8) - def test_flip_transformer(self): - """Tests that FlipTransformer(Cooperator) == Defector.""" - p1 = axl.Cooperator() - p2 = FlipTransformer()(axl.Cooperator)() # Defector - match = axl.Match((p1, p2), turns=3) - results = match.play() - self.assertEqual(results, [(C, D), (C, D), (C, D)]) + cls1 = FinalTransformer([D, D])(InitialTransformer([D, D])(axl.Cooperator)) + p1 = cls1() + p2 = axl.Cooperator() + self.versus_test(p1, p2, [D, D, C, C, C, C, D, D], [C] * 8) + + def test_compose_transformers(self): + """Tests explicitly that transformations can be chained or composed using + compose_transformers.""" + cls1 = compose_transformers( + FinalTransformer([D, D]), InitialTransformer([D, D]) + ) + p1 = cls1(axl.Cooperator)() + p2 = axl.Cooperator() + self.versus_test(p1, p2, [D, D, C, C, C, C, D, D], [C] * 8) + + def test_nilpotency(self): + """Show that some of the transformers are (sometimes) nilpotent, i.e. + that transformer(transformer(PlayerClass)) == PlayerClass""" + for transformer in [ + IdentityTransformer(), + FlipTransformer(), + TrackHistoryTransformer(), + ]: + for PlayerClass in [axl.Cooperator, axl.Defector]: + for third_player in [axl.Cooperator(), axl.Defector()]: + player = PlayerClass() + transformed = transformer(transformer(PlayerClass))() + clone = third_player.clone() + match = axl.Match((player, third_player), turns=5) + match.play() + match = axl.Match((transformed, clone), turns=5) + match.play() + self.assertEqual(player.history, transformed.history) + + def test_idempotency(self): + """Show that these transformers are idempotent, i.e. that + transformer(transformer(PlayerClass)) == transformer(PlayerClass). + That means that the transformer is a projection on the set of + strategies.""" + for transformer in [ + IdentityTransformer(), + GrudgeTransformer(1), + FinalTransformer([C]), + FinalTransformer([D]), + InitialTransformer([C]), + InitialTransformer([D]), + DeadlockBreakingTransformer(), + RetaliationTransformer(1), + RetaliateUntilApologyTransformer(), + TrackHistoryTransformer(), + ApologyTransformer([D], [C]), + ]: + for PlayerClass in [axl.Cooperator, axl.Defector]: + for third_player in [axl.Cooperator(), axl.Defector()]: + clone = third_player.clone() + player = transformer(PlayerClass)() + transformed = transformer(transformer(PlayerClass))() + match = axl.Match((player, third_player), turns=5) + match.play() + match = axl.Match((transformed, clone), turns=5) + match.play() + self.assertEqual(player.history, transformed.history) + + +class TestApologizingTransformer(TestMatch): + def test_apology(self): + """Tests the ApologyTransformer.""" + ApologizingDefector = ApologyTransformer([D], [C])(axl.Defector) + p1 = ApologizingDefector() + p2 = axl.Cooperator() + self.versus_test(p1, p2, [D, C, D, C, D], [C] * 5) + + def test_apology2(self): + ApologizingDefector = ApologyTransformer([D, D], [C, C])(axl.Defector) + p1 = ApologizingDefector() + p2 = axl.Cooperator() + self.versus_test(p1, p2, [D, D, C, D, D, C], [C] * 6) + + +class TestDeadlockBreakingTransformer(TestMatch): + def test_deadlock_breaks(self): + """Test the DeadlockBreakingTransformer.""" + # We can induce a deadlock by altering TFT to defect first + # No seed needed. + self.versus_test( + axl.TitForTat(), + InitialTransformer([D])(axl.TitForTat)(), + [C, D, C, D], + [D, C, D, C]) + + # Now let's use the transformer to break the deadlock to achieve + # Mutual cooperation + # self.versus_test( + self.versus_test( + axl.TitForTat(), + DeadlockBreakingTransformer()(InitialTransformer([D])(axl.TitForTat))(), + [C, D, C, C], + [D, C, C, C]) + + +class TestDualTransformer(TestMatch): + def assert_dual_wrapper_correct(self, player_class): + """Show that against an identical opponent, the dual transformer + reverses all actions correctly.""" + turns = 20 + seed = 1 + + p1 = player_class() + p2 = DualTransformer()(player_class)() + p3 = axl.CyclerCCD() # Cycles 'CCD' + + match = axl.Match((p1, p3), turns=turns, seed=seed) + match.play() + p3.reset() + match = axl.Match((p2, p3), turns=turns, seed=seed) + match.play() + + self.assertEqual(p1.history, [x.flip() for x in p2.history]) def test_dual_transformer_with_all_strategies(self): """Tests that DualTransformer produces the opposite results when faced @@ -228,12 +326,10 @@ def test_dual_transformer_simple_play_regression_test(self): DualTransformer()(axl.Cooperator) )() - match = axl.Match((multiple_dual_transformers, dual_transformer_not_first), - turns=3) - match.play() - - self.assertEqual(multiple_dual_transformers.history, [D, D, D]) - self.assertEqual(dual_transformer_not_first.history, [D, D, D]) + self.versus_test(multiple_dual_transformers, + dual_transformer_not_first, + [D, D, D], + [D, D, D]) def test_dual_transformer_multiple_interspersed_regression_test(self): """DualTransformer has failed when there were multiple DualTransformers. @@ -250,120 +346,125 @@ def test_dual_transformer_multiple_interspersed_regression_test(self): ) self.assert_dual_wrapper_correct(multiple_dual_transformers) - def assert_dual_wrapper_correct(self, player_class): - turns = 100 - p1 = player_class() - p2 = DualTransformer()(player_class)() - p3 = axl.CyclerCCD() # Cycles 'CCD' +class TestFinalTransformer(TestMatch): + def test_final_transformer(self): + """Tests the FinalTransformer when tournament length is known.""" + # Final play transformer + p1 = axl.Cooperator() + p2 = FinalTransformer([D, D, D])(axl.Cooperator)() + self.assertEqual(axl.Classifiers["makes_use_of"](p2), set(["length"])) + self.assertEqual(axl.Classifiers["memory_depth"](p2), 3) + self.assertEqual(axl.Classifiers["makes_use_of"](axl.Cooperator()), set([])) + self.versus_test(p1, p2, [C] * 8, [C, C, C, C, C, D, D, D], turns=8) - match = axl.Match((p1, p3), turns=3, seed=0) - match.play() - p3.reset() + def test_infinite_memory_depth_transformed(self): + # Test on infinite memory depth, that memory depth isn't set to + # a finite value + p3 = FinalTransformer([D, D])(axl.Adaptive)() + self.assertEqual(axl.Classifiers["memory_depth"](p3), float("inf")) - match = axl.Match((p2, p3), turns=3, seed=0) - match.play() + def test_final_transformer_unknown_length(self): + """Tests the FinalTransformer when tournament length is not known.""" + p1 = axl.Defector() + p2 = FinalTransformer([D, D])(axl.Cooperator)() + self.versus_test(p1, p2, [D] * 6, [C] * 6, + match_attributes={"length": -1}) - self.assertEqual(p1.history, [x.flip() for x in p2.history]) - def test_jossann_transformer(self): - """Tests the JossAnn transformer. - """ - probability = (1, 0) - p1 = JossAnnTransformer(probability)(axl.Defector)() - self.assertFalse(axl.Classifiers["stochastic"](p1)) - p2 = axl.Cooperator() - match = axl.Match((p1, p2), turns=5, seed=0) - match.play() - self.assertEqual(p1.history, [C, C, C, C, C]) +class TestFlipTransformer(TestMatch): + def test_flip_transformer(self): + """Tests that FlipTransformer(Cooperator) == Defector.""" + p1 = axl.Cooperator() + p2 = FlipTransformer()(axl.Cooperator)() # Defector + self.versus_test(p1, p2, [C] * 5, [D] * 5) - probability = (0, 1) - p1 = JossAnnTransformer(probability)(axl.Cooperator)() - self.assertFalse(axl.Classifiers["stochastic"](p1)) - match = axl.Match((p1, p2), turns=5, seed=0) - match.play() - self.assertEqual(p1.history, [D, D, D, D, D]) + def test_implementation(self): + """A test that demonstrates the difference in outcomes if + FlipTransformer is applied to Alternator and CyclerCD. In other words, + the implementation matters, not just the outcomes.""" + # Difference between Alternator and CyclerCD + p1 = axl.Cycler(cycle="CD") + p2 = FlipTransformer()(axl.Cycler)(cycle="CD") + self.versus_test(p1, p2, [C, D, C, D, C], [D, C, D, C, D]) - probability = (0.3, 0.3) - p1 = JossAnnTransformer(probability)(axl.TitForTat)() - self.assertTrue(axl.Classifiers["stochastic"](p1)) + p1 = axl.Alternator() + p2 = FlipTransformer()(axl.Alternator)() + self.versus_test(p1, p2, [C, D, C, D, C], [D, D, D, D, D]) - p2 = axl.Cycler() - match = axl.Match((p1, p2), turns=5, seed=0) - match.play() - self.assertEqual(p1.history, [D, C, C, D, D]) - probability = (0.6, 0.6) - p1 = JossAnnTransformer(probability)(axl.Cooperator)() - self.assertTrue(axl.Classifiers["stochastic"](p1)) - p2 = axl.Cooperator() - match = axl.Match((p1, p2), turns=5, seed=0) - match.play() +class TestForgivingTransformer(TestMatch): + def test_forgiving_transformer(self): + """Tests that the forgiving transformer flips some defections.""" + p1 = ForgiverTransformer(0.5)(axl.Alternator)() + p2 = axl.Defector() + turns = 10 + self.versus_test(p1, p2, [C, D, C, C, D, C, C, D, C, D], [D] * turns, + seed=8) - self.assertEqual(p1.history, [D, C, D, D, C]) + def test_stochastic_values_classifier(self): + p1 = ForgiverTransformer(0.5)(axl.Alternator)() + self.assertTrue(axl.Classifiers["stochastic"](p1)) - probability = (0, 1) - p1 = JossAnnTransformer(probability)(axl.Random) - self.assertFalse(axl.Classifiers["stochastic"](p1())) + def test_deterministic_values_classifier(self): + p1 = ForgiverTransformer(0)(axl.Alternator)() + self.assertFalse(axl.Classifiers["stochastic"](p1)) - probability = (1, 0) - p1 = JossAnnTransformer(probability)(axl.Random) - self.assertFalse(axl.Classifiers["stochastic"](p1())) + p1 = ForgiverTransformer(1)(axl.Alternator)() + self.assertFalse(axl.Classifiers["stochastic"](p1)) - probability = (0.5, 0.5) - p1 = JossAnnTransformer(probability)(axl.TitForTat) - self.assertTrue(axl.Classifiers["stochastic"](p1())) - probability = (0, 0.5) - p1 = JossAnnTransformer(probability)(axl.TitForTat) - self.assertTrue(axl.Classifiers["stochastic"](p1())) +class TestGrudgingTransformer(TestMatch): + def test_grudging1(self): + p1 = axl.Defector() + p2 = GrudgeTransformer(1)(axl.Cooperator)() + self.versus_test(p1, p2, [D, D, D, D], [C, C, D, D], seed=11) - probability = (0, 0) - p1 = JossAnnTransformer(probability)(axl.TitForTat) - self.assertFalse(axl.Classifiers["stochastic"](p1())) + def test_grudging2(self): + p1 = InitialTransformer([C])(axl.Defector)() + p2 = GrudgeTransformer(2)(axl.Cooperator)() + self.versus_test(p1, p2, [C, D, D, D, D, D, D, D], [C, C, C, C, D, D, D, D], seed=11) - probability = (0, 0) - p1 = JossAnnTransformer(probability)(axl.Random) - self.assertTrue(axl.Classifiers["stochastic"](p1())) - def test_noisy_transformer(self): - """Tests that the noisy transformed does flip some moves.""" - # Cooperator to Defector +class TestHistoryTrackingTransformer(TestMatch): + def test_history_track(self): + """Tests the tracked history matches.""" p1 = axl.Cooperator() - p2 = NoisyTransformer(0.5)(axl.Cooperator)() - self.assertTrue(axl.Classifiers["stochastic"](p2)) - match = axl.Match((p1, p2), turns=10, seed=5) + p2 = TrackHistoryTransformer()(axl.Random)() + match = axl.Match((p1, p2), turns=6, seed=1) match.play() - self.assertEqual(p2.history, [C, C, C, C, C, C, D, D, C, C]) - - def test_noisy_transformatino_stochastic(self): - p2 = NoisyTransformer(1)(axl.Cooperator) - self.assertFalse(axl.Classifiers["stochastic"](p2())) + self.assertEqual(p2.history, p2._recorded_history) - p2 = NoisyTransformer(0.3)(axl.Cooperator) - self.assertTrue(axl.Classifiers["stochastic"](p2())) + def test_actions_unaffected(self): + """Tests that the history tracking transformer doesn't alter the play at all.""" + p1 = axl.Cooperator() + p2 = TrackHistoryTransformer()(axl.Alternator)() + self.versus_test(p1, p2, [C] * 8, [C, D] * 4) - p2 = NoisyTransformer(0)(axl.Random) - self.assertTrue(axl.Classifiers["stochastic"](p2())) - p2 = NoisyTransformer(1)(axl.Random) - self.assertTrue(axl.Classifiers["stochastic"](p2())) +class TestIdentityTransformer(TestMatch): + def test_all_strategies(self): + # Attempt to transform each strategy to ensure that implementation + # choices (like use of super) do not cause issues + for s in axl.strategies: + opponent = axl.Cooperator() + player = IdentityTransformer()(s)() + match = axl.Match((player, opponent), turns=3) + match.play() - def test_forgiving(self): - """Tests that the forgiving transformer flips some defections.""" - p1 = ForgiverTransformer(0.5)(axl.Alternator)() - self.assertTrue(axl.Classifiers["stochastic"](p1)) - p2 = axl.Defector() - match = axl.Match((p1, p2), turns=10, seed=10) - match.play() - self.assertEqual(p1.history, [C, D, C, C, D, C, C, D, C, D]) + def test_generic(self): + """Test that the generic wrapper does nothing.""" + # This is the identity transformer + transformer = StrategyTransformerFactory(generic_strategy_wrapper)() + Cooperator2 = transformer(axl.Cooperator) + Defector2 = transformer(axl.Defector) - p1 = ForgiverTransformer(0)(axl.Alternator)() - self.assertFalse(axl.Classifiers["stochastic"](p1)) + turns = 100 + self.versus_test(axl.Cooperator(), Cooperator2(), [C] * turns, [C] * turns) + self.versus_test(axl.Cooperator(), Defector2(), [C] * turns, [D] * turns) - p1 = ForgiverTransformer(1)(axl.Alternator)() - self.assertFalse(axl.Classifiers["stochastic"](p1)) +class TestInitialTransformer(TestMatch): def test_initial_transformer(self): """Tests the InitialTransformer.""" p1 = axl.Cooperator() @@ -383,139 +484,71 @@ def test_initial_transformer(self): p3 = InitialTransformer([D, D])(axl.Adaptive)() self.assertEqual(axl.Classifiers["memory_depth"](p3), float("inf")) - def test_final_transformer(self): - """Tests the FinalTransformer when tournament length is known.""" - # Final play transformer - p1 = axl.Cooperator() - p2 = FinalTransformer([D, D, D])(axl.Cooperator)() - self.assertEqual(axl.Classifiers["makes_use_of"](p2), set(["length"])) - self.assertEqual(axl.Classifiers["memory_depth"](p2), 3) - self.assertEqual(axl.Classifiers["makes_use_of"](axl.Cooperator()), set([])) - - # p2.match_attributes["length"] = 6 - match = axl.Match((p1, p2), turns=8, seed=0) - match.play() - - self.assertEqual(p2.history, [C, C, C, D, D, D, C, C]) - - p3 = FinalTransformer([D, D])(axl.Adaptive)() - self.assertEqual(axl.Classifiers["memory_depth"](p3), float("inf")) - - def test_final_transformer2(self): - """Tests the FinalTransformer when tournament length is not known.""" - p1 = axl.Cooperator() - p2 = FinalTransformer([D, D])(axl.Cooperator)() - match = axl.Match((p1, p2), turns=6, seed=0) - match.play() - self.assertEqual(p2.history, [C, C, C, C, C, C]) - - def test_history_track(self): - """Tests the history tracking transformer.""" - p1 = axl.Cooperator() - p2 = TrackHistoryTransformer()(axl.Random)() - match = axl.Match((p1, p2), turns=6, seed=0) - match.play() - self.assertEqual(p2.history, p2._recorded_history) - def test_composition(self): - """Tests that transformations can be chained or composed.""" - cls1 = InitialTransformer([D, D])(axl.Cooperator) - cls2 = FinalTransformer([D, D])(cls1) - p1 = cls2() +class TestJossAnnTransformer(TestMatch): + def test_deterministic_match(self): + """Tests the JossAnn transformer.""" + probability = (1, 0) + p1 = JossAnnTransformer(probability)(axl.Defector)() + self.assertFalse(axl.Classifiers["stochastic"](p1)) p2 = axl.Cooperator() - match = axl.Match((p1, p2), turns=8, seed=0) - match.play() - self.assertEqual(p1.history, [D, D, C, C, C, C, D, D]) + self.versus_test(p1, p2, [C] * 5, [C] * 5) - cls1 = FinalTransformer([D, D])(InitialTransformer([D, D])(axl.Cooperator)) - p1 = cls1() - p2 = axl.Cooperator() - match = axl.Match((p1, p2), turns=8, seed=0) - match.play() - self.assertEqual(p1.history, [D, D, C, C, C, C, D, D]) + probability = (0, 1) + p1 = JossAnnTransformer(probability)(axl.Cooperator)() + self.assertFalse(axl.Classifiers["stochastic"](p1)) + self.versus_test(p1, p2, [D] * 5, [C] * 5) - def test_compose_transformers(self): - cls1 = compose_transformers( - FinalTransformer([D, D]), InitialTransformer([D, D]) - ) - p1 = cls1(axl.Cooperator)() - p2 = axl.Cooperator() - match = axl.Match((p1, p2), turns=8, seed=0) - match.play() - self.assertEqual(p1.history, [D, D, C, C, C, C, D, D]) + def test_stochastic1(self): + probability = (0.3, 0.3) + p1 = JossAnnTransformer(probability)(axl.TitForTat)() + self.assertTrue(axl.Classifiers["stochastic"](p1)) + p2 = axl.Cycler() + self.versus_test(p1, p2, [D, C, C, D, D], [C, C, D, C, C], seed=18) - def test_retailiation(self): - """Tests the RetaliateTransformer.""" - p1 = RetaliationTransformer(1)(axl.Cooperator)() - p2 = axl.Defector() - match = axl.Match((p1, p2), turns=5, seed=0) - match.play() - self.assertEqual(p1.history, [C, D, D, D, D]) - self.assertEqual(p2.history, [D, D, D, D, D]) + def test_stochastic2(self): + probability = (0.6, 0.6) + p1 = JossAnnTransformer(probability)(axl.Cooperator)() + self.assertTrue(axl.Classifiers["stochastic"](p1)) + p2 = axl.Cooperator() + self.versus_test(p1, p2, [D, C, D, D, C], [C] * 5, seed=27) - p1 = RetaliationTransformer(1)(axl.Cooperator)() - p2 = axl.Alternator() - match = axl.Match((p1, p2), turns=5, seed=0) - match.play() - self.assertEqual(p1.history, [C, C, D, C, D]) - self.assertEqual(p2.history, [C, D, C, D, C]) + def test_stochastic_classifiers(self): + probability = (0, 1) + p1 = JossAnnTransformer(probability)(axl.Random) + self.assertFalse(axl.Classifiers["stochastic"](p1())) - TwoTitsForTat = RetaliationTransformer(2)(axl.Cooperator) - p1 = TwoTitsForTat() - p2 = axl.CyclerCCD() - match = axl.Match((p1, p2), turns=9, seed=0) - match.play() - self.assertEqual(p1.history, [C, C, C, D, D, C, D, D, C]) - self.assertEqual(p2.history, [C, C, D, C, C, D, C, C, D]) + probability = (1, 0) + p1 = JossAnnTransformer(probability)(axl.Random) + self.assertFalse(axl.Classifiers["stochastic"](p1())) - def test_retaliation_until_apology(self): - """Tests the RetaliateUntilApologyTransformer.""" - TFT = RetaliateUntilApologyTransformer()(axl.Cooperator) - p1 = TFT() - p2 = axl.Cooperator() - match = axl.Match((p1, p2), turns=2) - match.play() - self.assertEqual(p1.history, [C, C]) + probability = (0.5, 0.5) + p1 = JossAnnTransformer(probability)(axl.TitForTat) + self.assertTrue(axl.Classifiers["stochastic"](p1())) - p1 = TFT() - p2 = axl.Defector() - match = axl.Match((p1, p2), turns=2) - match.play() - self.assertEqual(p1.history, [C, D]) + probability = (0, 0.5) + p1 = JossAnnTransformer(probability)(axl.TitForTat) + self.assertTrue(axl.Classifiers["stochastic"](p1())) - p1 = TFT() - p2 = axl.Random() - match = axl.Match((p1, p2), turns=5, seed=12) - match.play() - self.assertEqual(p1.history, [C, C, D, D, C]) + probability = (0, 0) + p1 = JossAnnTransformer(probability)(axl.TitForTat) + self.assertFalse(axl.Classifiers["stochastic"](p1())) - def test_apology(self): - """Tests the ApologyTransformer.""" - ApologizingDefector = ApologyTransformer([D], [C])(axl.Defector) - p1 = ApologizingDefector() - p2 = axl.Cooperator() - match = axl.Match((p1, p2), turns=5) - match.play() - self.assertEqual(p1.history, [D, C, D, C, D]) + probability = (0, 0) + p1 = JossAnnTransformer(probability)(axl.Random) + self.assertTrue(axl.Classifiers["stochastic"](p1())) - ApologizingDefector = ApologyTransformer([D, D], [C, C])(axl.Defector) - p1 = ApologizingDefector() - p2 = axl.Cooperator() - match = axl.Match((p1, p2), turns=6, seed=0) - match.play() - self.assertEqual(p1.history, [D, D, C, D, D, C]) - def test_mixed(self): - """Tests the MixedTransformer.""" +class TestMixedTransformer(TestMatch): + def test_mixed_transformer_deterministic(self): + """Test changes in stochasticity.""" probability = 1 MD = MixedTransformer(probability, axl.Cooperator)(axl.Defector) self.assertFalse(axl.Classifiers["stochastic"](MD())) p1 = MD() p2 = axl.Cooperator() - match = axl.Match((p1, p2), turns=5) - match.play() - self.assertEqual(p1.history, [C, C, C, C, C]) + self.versus_test(p1, p2, [C] * 5, [C] * 5) probability = 0 MD = MixedTransformer(probability, axl.Cooperator)(axl.Defector) @@ -523,10 +556,9 @@ def test_mixed(self): p1 = MD() p2 = axl.Cooperator() - match = axl.Match((p1, p2), turns=5) - match.play() - self.assertEqual(p1.history, [D, D, D, D, D]) + self.versus_test(p1, p2, [D] * 5, [C] * 5) + def test_mixed_transformer(self): # Decorating with list and distribution # Decorate a cooperator putting all weight on other strategies that are # 'nice' @@ -538,9 +570,7 @@ def test_mixed(self): p1 = MD() # Against a cooperator we see that we only cooperate p2 = axl.Cooperator() - match = axl.Match((p1, p2), turns=5) - match.play() - self.assertEqual(p1.history, [C, C, C, C, C]) + self.versus_test(p1, p2, [C] * 5, [C] * 5) # Decorate a cooperator putting all weight on Defector probability = (0, 0, 1) # Note can also pass tuple @@ -551,142 +581,97 @@ def test_mixed(self): p1 = MD() # Against a cooperator we see that we only defect p2 = axl.Cooperator() - match = axl.Match((p1, p2), turns=5) - match.play() - self.assertEqual(p1.history, [D, D, D, D, D]) - - def test_deadlock(self): - """Test the DeadlockBreakingTransformer.""" - # We can induce a deadlock by alterting TFT to defect first - p1 = axl.TitForTat() - p2 = InitialTransformer([D])(axl.TitForTat)() - match = axl.Match((p1, p2), turns=4) - match.play() - self.assertEqual(p1.history, [C, D, C, D]) - self.assertEqual(p2.history, [D, C, D, C]) - - # Now let's use the transformer to break the deadlock to achieve - # Mutual cooperation - p1 = axl.TitForTat() - p2 = DeadlockBreakingTransformer()(InitialTransformer([D])(axl.TitForTat))() - match = axl.Match((p1, p2), turns=45, seed=0) - match.play() - self.assertEqual(p1.history, [C, D, C, C]) - self.assertEqual(p2.history, [D, C, C, C]) + self.versus_test(p1, p2, [D] * 5, [C] * 5) - def test_grudging(self): - """Test the GrudgeTransformer.""" - p1 = axl.Defector() - p2 = GrudgeTransformer(1)(axl.Cooperator)() - match = axl.Match((p1, p2), turns=4, seed=0) - match.play() - self.assertEqual(p1.history, [D, D, D, D]) - self.assertEqual(p2.history, [C, C, D, D]) - - p1 = InitialTransformer([C])(axl.Defector)() - p2 = GrudgeTransformer(2)(axl.Cooperator)() - match = axl.Match((p1, p2), turns=8, seed=0) - match.play() - self.assertEqual(p1.history, [C, D, D, D, D, D, D, D]) - self.assertEqual(p2.history, [C, C, C, C, D, D, D, D]) - def test_nice(self): +class TestNiceTransformer(TestMatch): + def test_nice1(self): """Tests the NiceTransformer.""" p1 = NiceTransformer()(axl.Defector)() p2 = axl.Defector() - match = axl.Match((p1, p2), turns=5) - match.play() - self.assertEqual(p1.history, [C, D, D, D, D]) - self.assertEqual(p2.history, [D, D, D, D, D]) + self.versus_test(p1, p2, [C, D, D, D, D], [D, D, D, D, D]) + def test_nice2(self): p1 = NiceTransformer()(axl.Defector)() p2 = axl.Alternator() - match = axl.Match((p1, p2), turns=5) - match.play() - self.assertEqual(p1.history, [C, C, D, D, D]) - self.assertEqual(p2.history, [C, D, C, D, C]) + self.versus_test(p1, p2, [C, C, D, D, D], [C, D, C, D, C]) + def test_nice3(self): p1 = NiceTransformer()(axl.Defector)() p2 = axl.Cooperator() - match = axl.Match((p1, p2), turns=5) - match.play() - self.assertEqual(p1.history, [C, C, C, C, C]) - self.assertEqual(p2.history, [C, C, C, C, C]) + self.versus_test(p1, p2, [C, C, C, C, C], [C, C, C, C, C]) - def test_nilpotency(self): - """Show that some of the transformers are (sometimes) nilpotent, i.e. - that transformer(transformer(PlayerClass)) == PlayerClass""" - for transformer in [ - IdentityTransformer(), - FlipTransformer(), - TrackHistoryTransformer(), - ]: - for PlayerClass in [axl.Cooperator, axl.Defector]: - for third_player in [axl.Cooperator(), axl.Defector()]: - player = PlayerClass() - transformed = transformer(transformer(PlayerClass))() - clone = third_player.clone() - match = axl.Match((player, third_player), turns=5) - match.play() - match = axl.Match((transformed, clone), turns=5) - match.play() - self.assertEqual(player.history, transformed.history) - def test_idempotency(self): - """Show that these transformers are idempotent, i.e. that - transformer(transformer(PlayerClass)) == transformer(PlayerClass). - That means that the transformer is a projection on the set of - strategies.""" - for transformer in [ - IdentityTransformer(), - GrudgeTransformer(1), - FinalTransformer([C]), - FinalTransformer([D]), - InitialTransformer([C]), - InitialTransformer([D]), - DeadlockBreakingTransformer(), - RetaliationTransformer(1), - RetaliateUntilApologyTransformer(), - TrackHistoryTransformer(), - ApologyTransformer([D], [C]), - ]: - for PlayerClass in [axl.Cooperator, axl.Defector]: - for third_player in [axl.Cooperator(), axl.Defector()]: - clone = third_player.clone() - player = transformer(PlayerClass)() - transformed = transformer(transformer(PlayerClass))() - match = axl.Match((player, third_player), turns=5) - match.play() - match = axl.Match((transformed, clone), turns=5) - match.play() - self.assertEqual(player.history, transformed.history) +class TestNoisyTransformer(TestMatch): + def test_noisy_transformer(self): + """Tests that the noisy transformed does flip some moves.""" + # Cooperator to Defector + p1 = axl.Cooperator() + p2 = NoisyTransformer(0.5)(axl.Cooperator)() + self.assertTrue(axl.Classifiers["stochastic"](p2)) + self.versus_test(p1, p2, [C] * 10, [C, C, C, C, C, C, D, D, C, C], seed=344) - def test_implementation(self): - """A test that demonstrates the difference in outcomes if - FlipTransformer is applied to Alternator and CyclerCD. In other words, - the implementation matters, not just the outcomes.""" - # Difference between Alternator and CyclerCD - p1 = axl.Cycler(cycle="CD") - p2 = FlipTransformer()(axl.Cycler)(cycle="CD") - match = axl.Match((p1, p2), turns=5) - match.play() - self.assertEqual(p1.history, [C, D, C, D, C]) - self.assertEqual(p2.history, [D, C, D, C, D]) + def test_noisy_transformation_stochastic(self): + """Depending on the value of the noise parameter, the strategy may become stochastic + or deterministic.""" + # Deterministic --> Deterministic + p2 = NoisyTransformer(1)(axl.Cooperator) + self.assertFalse(axl.Classifiers["stochastic"](p2())) - p1 = axl.Alternator() - p2 = FlipTransformer()(axl.Alternator)() - match = axl.Match((p1, p2), turns=5) - match.play() - self.assertEqual(p1.history, [C, D, C, D, C]) - self.assertEqual(p2.history, [D, D, D, D, D]) + # Deterministic --> Stochastic + p2 = NoisyTransformer(0.3)(axl.Cooperator) + self.assertTrue(axl.Classifiers["stochastic"](p2())) + # Stochastic --> Deterministic, case 0 + p2 = NoisyTransformer(0)(axl.Random) + self.assertTrue(axl.Classifiers["stochastic"](p2())) -TFT = RetaliateUntilApologyTransformer()(axl.Cooperator) + # Stochastic --> Deterministic, case 1 + p2 = NoisyTransformer(1)(axl.Random) + self.assertTrue(axl.Classifiers["stochastic"](p2())) + + +class TestRetailiateTransformer(TestMatch): + def test_retailiating_cooperator_against_defector(self): + """Tests the RetaliateTransformer.""" + p1 = RetaliationTransformer(1)(axl.Cooperator)() + p2 = axl.Defector() + self.versus_test(p1, p2, [C, D, D, D, D], [D, D, D, D, D]) + + def test_retailiating_cooperator_against_alternator(self): + p1 = RetaliationTransformer(1)(axl.Cooperator)() + p2 = axl.Alternator() + self.versus_test(p1, p2, [C, C, D, C, D], [C, D, C, D, C]) + + def test_retailiating_cooperator_against_2TFT(self): + TwoTitsForTat = RetaliationTransformer(2)(axl.Cooperator) + p1 = TwoTitsForTat() + p2 = axl.CyclerCCD() + self.versus_test(p1, p2, [C, C, C, D, D, C, D, D, C], [C, C, D, C, C, D, C, C, D]) + + +class TestRetailiateUntilApologyTransformer(TestMatch): + def test_retaliation_until_apology(self): + """Tests the RetaliateUntilApologyTransformer.""" + TFT = RetaliateUntilApologyTransformer()(axl.Cooperator) + p1 = TFT() + p2 = axl.Cooperator() + self.versus_test(p1, p2, [C, C], [C, C]) + + p1 = TFT() + p2 = axl.Defector() + self.versus_test(p1, p2, [C, D], [D, D]) + + def test_retaliation_until_apology_stochastic(self): + TFT = RetaliateUntilApologyTransformer()(axl.Cooperator) + p1 = TFT() + p2 = axl.Random() + self.versus_test(p1, p2, [C, C, D, D, C], [C, D, D, C, C], seed=35) class TestRUAisTFT(TestTitForTat): # This runs the 7 TFT tests when unittest is invoked - player = TFT + player = RetaliateUntilApologyTransformer()(axl.Cooperator) name = "RUA Cooperator" expected_classifier = { "memory_depth": 0, # really 1 @@ -699,11 +684,7 @@ class TestRUAisTFT(TestTitForTat): } -# Test that FlipTransformer(Defector) == Cooperator -Cooperator2 = FlipTransformer()(axl.Defector) - - class TestFlipDefector(TestCooperator): - # This runs the 7 TFT tests when unittest is invoked + # Test that FlipTransformer(Defector) == Cooperator name = "Flipped Defector" - player = Cooperator2 + player = FlipTransformer()(axl.Defector) From 02ecf68d772336344e7c8c3d0e3673b356074f60 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sun, 5 Jul 2020 21:08:42 -0700 Subject: [PATCH 052/121] Add search_seeds method to TestMatch --- axelrod/tests/strategies/test_player.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/axelrod/tests/strategies/test_player.py b/axelrod/tests/strategies/test_player.py index 9c5294420..2cfa40708 100644 --- a/axelrod/tests/strategies/test_player.py +++ b/axelrod/tests/strategies/test_player.py @@ -618,7 +618,6 @@ class TestMatch(unittest.TestCase): """Test class for heads up play between two given players. Plays an axelrod match between the two players.""" - @log_kwargs def versus_test( self, player1, @@ -653,6 +652,22 @@ def versus_test( for attr, value in attrs.items(): self.assertEqual(getattr(player1, attr), value) + def search_seeds(self, *args, **kwargs): + """Search for a seed that will pass the test. To use to find a new seed + for a versus_test, change self.versus_test to self.search_seeds + within a TestPlayer or TestMatch class. + """ + for seed in range(1, 100000): + try: + kwargs["seed"] = seed + self.versus_test(*args, **kwargs) + except AssertionError: + continue + else: + print(seed) + return seed + return None + def test_versus_with_incorrect_history_lengths(self): """Test the error raised by versus_test if expected actions do not match up.""" From 14ba128ca53f97bd3a844d7a35c680aa11f9cba8 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sun, 5 Jul 2020 21:49:51 -0700 Subject: [PATCH 053/121] Make JossAnn and Mixed transformers avoid use of _random when they are actually deterministic. --- axelrod/strategy_transformers.py | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/axelrod/strategy_transformers.py b/axelrod/strategy_transformers.py index 8c411f647..609b26476 100644 --- a/axelrod/strategy_transformers.py +++ b/axelrod/strategy_transformers.py @@ -531,18 +531,19 @@ def mixed_wrapper(player, opponent, action, probability, m_player): m_player = [m_player] probability = [probability] - # If a probability distribution, players is passed - if isinstance(probability, Iterable) and isinstance( - m_player, Iterable - ): - mutate_prob = sum(probability) # Prob of mutation - if mutate_prob > 0: - # Distribution of choice of mutation: - normalised_prob = [prob / mutate_prob for prob in probability] - if player._random.random() < mutate_prob: - p = player._random.choice(list(m_player), p=normalised_prob)() - p._history = player._history - return p.strategy(opponent) + mutate_prob = sum(probability) # Prob of mutation + if mutate_prob > 0: + # Distribution of choice of mutation: + normalised_prob = [prob / mutate_prob for prob in probability] + # Check if the strategy is deterministic. If so, avoid use of + # self._random, since it may not be present on the host strategy. + if 1 in probability: # If all probability given to one player + p = m_player[probability.index(1)] + return p.strategy(opponent) + elif player._random.random() < mutate_prob: + p = player._random.choice(list(m_player), p=normalised_prob)() + p._history = player._history + return p.strategy(opponent) return action @@ -603,6 +604,12 @@ def joss_ann_wrapper(player, opponent, proposed_action, probability): remaining_probability = max(0, 1 - probability[0] - probability[1]) probability += (remaining_probability,) options = [C, D, proposed_action] + + # Avoid use of self._random if strategy is actually deterministic. + if 1 in probability: # If all probability given to one player + option = options[probability.index(1)] + return option + action = player._random.choice(options, p=probability) return action From 69ac7f415d34cbc5f1f63050943545e53d545f35 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sun, 5 Jul 2020 23:01:04 -0700 Subject: [PATCH 054/121] Rework Random strategy to avoid using random numbers if actually deterministic --- axelrod/strategies/rand.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/axelrod/strategies/rand.py b/axelrod/strategies/rand.py index 74b050af2..ab1d87cbc 100644 --- a/axelrod/strategies/rand.py +++ b/axelrod/strategies/rand.py @@ -2,6 +2,17 @@ from axelrod.player import Player +C, D = Action.C, Action.D + + +def cooperate(*args): + return C + + +def defect(*args): + return D + + class Random(Player): """A player who randomly chooses between cooperating and defecting. @@ -40,6 +51,20 @@ def __init__(self, p: float = 0.5) -> None: self.p = p if p in [0, 1]: self.classifier["stochastic"] = False + # Avoid calls to _random, if strategy is deterministic + # by overwriting the strategy function. + if p == 0: + self.strategy = self.cooperate + if p == 1: + self.strategy = self.defect + + @classmethod + def cooperate(cls, opponent: Player) -> Action: + return C + + @classmethod + def defect(cls, opponent: Player) -> Action: + return D def strategy(self, opponent: Player) -> Action: return self._random.random_choice(self.p) From 28419c61e73d73d2ce256201794860d38bbd4a44 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sun, 5 Jul 2020 23:01:53 -0700 Subject: [PATCH 055/121] Remove redundant code from Random --- axelrod/strategies/rand.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/axelrod/strategies/rand.py b/axelrod/strategies/rand.py index ab1d87cbc..5cfd9f0e8 100644 --- a/axelrod/strategies/rand.py +++ b/axelrod/strategies/rand.py @@ -5,14 +5,6 @@ C, D = Action.C, Action.D -def cooperate(*args): - return C - - -def defect(*args): - return D - - class Random(Player): """A player who randomly chooses between cooperating and defecting. From e6f18f337966201d1015ea4edcf64530e3a1d348 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sun, 5 Jul 2020 23:13:07 -0700 Subject: [PATCH 056/121] Fix several issues with MetaMixer --- axelrod/strategies/meta.py | 34 +++++++++++++++++++++++++-- axelrod/tests/strategies/test_meta.py | 27 +++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/axelrod/strategies/meta.py b/axelrod/strategies/meta.py index 33a914042..101b35f19 100644 --- a/axelrod/strategies/meta.py +++ b/axelrod/strategies/meta.py @@ -496,11 +496,41 @@ class MetaMixer(MetaPlayer): } def __init__(self, team=None, distribution=None): - self.distribution = distribution super().__init__(team=team) + if distribution and len(set(distribution)) > 1: + self.classifier["stochastic"] = True + if len(self.team) == 1: + self.classifier["stochastic"] = Classifiers["stochastic"](self.team[0]) + # Overwrite strategy to avoid use of _random. This will ignore self.meta_strategy. + self.index = 0 + self.strategy = self.index_strategy + return + # Check if the distribution has only one non-zero value. If so, the strategy may be + # deterministic, and we can avoid _random. + self.distribution = distribution + if distribution: + total = sum(distribution) + if total == 0: + return + distribution = np.array(distribution) / total + if 1 in distribution: + self.index = list(distribution).index(1) + # It's potentially deterministic. + self.classifier["stochastic"] = Classifiers["stochastic"](self.team[self.index]) + # Overwrite strategy to avoid use of _random. This will ignore self.meta_strategy. + self.strategy = self.index_strategy + + def index_strategy(self, opponent): + """When the team effectively has a single player, only use that strategy.""" + results = [C] * len(self.team) + player = self.team[self.index] + action = player.strategy(opponent) + results[self.index] = action + self._last_results = results + return action def meta_strategy(self, results, opponent): - """Using the numpy.random choice function to sample with weights""" + """Using the _random.choice function to sample with weights.""" return self._random.choice(results, p=self.distribution) diff --git a/axelrod/tests/strategies/test_meta.py b/axelrod/tests/strategies/test_meta.py index bfb5a51c3..ed9907dac 100644 --- a/axelrod/tests/strategies/test_meta.py +++ b/axelrod/tests/strategies/test_meta.py @@ -1,6 +1,7 @@ """Tests for the various Meta strategies.""" import axelrod as axl +from axelrod.classifier import Classifiers from hypothesis import given, settings from hypothesis.strategies import integers @@ -492,6 +493,32 @@ class TestMetaMixer(TestMetaPlayer): "stochastic": True, } + def test_stochasticity(self): + # If the distribution is deterministic, the strategy may be deterministic. + team = [axl.TitForTat, axl.Cooperator, axl.Grudger] + distribution = [1, 0, 0] + player = self.player(team=team, distribution=distribution) + self.assertFalse(Classifiers["stochastic"](player)) + + team = [axl.Random, axl.Cooperator, axl.Grudger] + distribution = [1, 0, 0] + player = self.player(team=team, distribution=distribution) + self.assertTrue(Classifiers["stochastic"](player)) + + # If the team has only one player, the strategy may be deterministic. + team = [axl.TitForTat] + player = self.player(team=team) + self.assertFalse(Classifiers["stochastic"](player)) + + team = [axl.Random] + player = self.player(team=team) + self.assertTrue(Classifiers["stochastic"](player)) + + # Stochastic if the distribution isn't degenerate. + team = [axl.TitForTat, axl.Cooperator, axl.Grudger] + distribution = [0.2, 0.5, 0.3] + self.assertTrue(Classifiers["stochastic"](player)) + def test_strategy(self): team = [axl.TitForTat, axl.Cooperator, axl.Grudger] distribution = [0.2, 0.5, 0.3] From f7e0b88381f8cbcf83610dc05df8f36bd92dd24f Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sun, 5 Jul 2020 23:37:40 -0700 Subject: [PATCH 057/121] Fix various issues, including stochastic classification, for MetaWinnerEnsemble --- axelrod/strategies/meta.py | 19 ++++++++++++++++-- axelrod/tests/strategies/test_meta.py | 28 +++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/axelrod/strategies/meta.py b/axelrod/strategies/meta.py index 101b35f19..6ac09ebfd 100644 --- a/axelrod/strategies/meta.py +++ b/axelrod/strategies/meta.py @@ -198,7 +198,7 @@ def meta_strategy(self, results, opponent): class MetaWinnerEnsemble(MetaWinner): """A variant of MetaWinner that chooses one of the top scoring strategies at random against each opponent. Note this strategy is always stochastic - regardless of the team. + regardless of the team, if team larger than 1, and the players are distinct. Names: @@ -207,13 +207,28 @@ class MetaWinnerEnsemble(MetaWinner): name = "Meta Winner Ensemble" + def __init__(self, team=None): + super().__init__(team=team) + if len(self.team) > 1: + self.classifier["stochastic"] = True + self.singular = False + else: + self.singular = True + if team and len(set(team)) == 1: + self.classifier["stochastic"] = Classifiers["stochastic"](team[0]()) + self.singular = True + def meta_strategy(self, results, opponent): + # If the team consists of identical players, just take the first result. + if self.singular: + return results[0] # Sort by score scores = [(score, i) for (i, score) in enumerate(self.scores)] # Choose one of the best scorers at random scores.sort(reverse=True) prop = max(1, int(len(scores) * 0.08)) - index = self._random.choice([i for (s, i) in scores[:prop]]) + best_scorers = [i for (s, i) in scores[:prop]] + index = self._random.choice(best_scorers) return results[index] diff --git a/axelrod/tests/strategies/test_meta.py b/axelrod/tests/strategies/test_meta.py index ed9907dac..c69bcbd6b 100644 --- a/axelrod/tests/strategies/test_meta.py +++ b/axelrod/tests/strategies/test_meta.py @@ -146,6 +146,33 @@ def test_strategy(self): self.assertEqual(P1.strategy(P2), C) +class TestMetaWinnerEnsemble(TestMetaPlayer): + name = "Meta Winner Ensemble" + player = axl.MetaWinnerEnsemble + + def test_stochasticity(self): + # One player teams may be stochastic or not + team = [axl.Cooperator] + player = axl.MetaWinnerEnsemble(team=team) + self.assertFalse(Classifiers["stochastic"](player)) + + team = [axl.Random] + player = axl.MetaWinnerEnsemble(team=team) + self.assertTrue(Classifiers["stochastic"](player)) + + # Multiplayer teams without repetition are always stochastic + team = [axl.Cooperator, axl.Defector] + player = axl.MetaWinnerEnsemble(team=team) + self.assertTrue(Classifiers["stochastic"](player)) + + # If the players are all identical, a multiplayer + # team might is in fact deterministic, even though random values + # are being drawn. + team = [axl.Cooperator, axl.Cooperator] + player = axl.MetaWinnerEnsemble(team=team) + self.assertFalse(Classifiers["stochastic"](player)) + + class TestNiceMetaWinner(TestMetaPlayer): name = "Nice Meta Winner" player = axl.NiceMetaWinner @@ -209,6 +236,7 @@ class TestNiceMetaWinnerEnsemble(TestMetaPlayer): "manipulates_state": False, } + def test_strategy(self): actions = [(C, C)] * 8 self.versus_test( From a796a2b82baca3885abfa2eae4ff3db91c1f91f1 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Tue, 7 Jul 2020 20:25:42 -0700 Subject: [PATCH 058/121] Avoid use of _random in deterministic probers --- axelrod/strategies/prober.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/axelrod/strategies/prober.py b/axelrod/strategies/prober.py index 6a5829bea..614d6634c 100644 --- a/axelrod/strategies/prober.py +++ b/axelrod/strategies/prober.py @@ -347,6 +347,10 @@ def strategy(self, opponent: Player) -> Action: if opponent.history[-1] == D: return D # Otherwise cooperate, defect with probability 1 - self.p + if self.p == 0: + return C + if self.p == 1: + return D choice = self._random.random_choice(1 - self.p) return choice @@ -395,7 +399,10 @@ def strategy(self, opponent: Player) -> Action: return D # Otherwise cooperate with probability 1 - self.p - if self._random.random() < 1 - self.p: + if self.p == 1: + self.probing = True + return D + elif self.p == 0 or self._random.random() < 1 - self.p: self.probing = False return C From 559f35b678101a35d4c4da3cd9356f2e6d4feb24 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Tue, 7 Jul 2020 20:26:04 -0700 Subject: [PATCH 059/121] Avoid use of _random in deterministic HMMs --- axelrod/strategies/hmm.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/axelrod/strategies/hmm.py b/axelrod/strategies/hmm.py index b43bba6e0..b4ecb59a4 100644 --- a/axelrod/strategies/hmm.py +++ b/axelrod/strategies/hmm.py @@ -111,11 +111,21 @@ def move(self, opponent_action: Action) -> Action: """ num_states = len(self.emission_probabilities) if opponent_action == C: - next_state = self._random.choice(num_states, 1, p=self.transitions_C[self.state]) + if 1 in self.transitions_C[self.state]: + next_state = [self.transitions_C[self.state].index(1)] + else: + next_state = self._random.choice(num_states, 1, p=self.transitions_C[self.state]) else: - next_state = self._random.choice(num_states, 1, p=self.transitions_D[self.state]) + if 1 in self.transitions_D[self.state]: + next_state = [self.transitions_D[self.state].index(1)] + else: + next_state = self._random.choice(num_states, 1, p=self.transitions_D[self.state]) self.state = next_state[0] p = self.emission_probabilities[self.state] + if p == 1: + return D + if p == 0: + return C action = self._random.random_choice(p) return action From 637c59bb76d9a335191daf5ad7070f5638d306c2 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Tue, 7 Jul 2020 20:26:34 -0700 Subject: [PATCH 060/121] Add comments to Meta --- axelrod/strategies/meta.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/axelrod/strategies/meta.py b/axelrod/strategies/meta.py index 6ac09ebfd..1869c52dc 100644 --- a/axelrod/strategies/meta.py +++ b/axelrod/strategies/meta.py @@ -214,12 +214,15 @@ def __init__(self, team=None): self.singular = False else: self.singular = True + # If the team has repeated identical members, then it reduces to a singular team + # and it may not actually be stochastic. if team and len(set(team)) == 1: self.classifier["stochastic"] = Classifiers["stochastic"](team[0]()) self.singular = True def meta_strategy(self, results, opponent): # If the team consists of identical players, just take the first result. + # This prevents an unnecessary call to _random below. if self.singular: return results[0] # Sort by score From 9e47ce3f7d2ff9a2cc7f1fbf27c3f8a64d3059bd Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Tue, 7 Jul 2020 20:27:35 -0700 Subject: [PATCH 061/121] In Match, only set seeds on players if they report that they are stochastic. --- axelrod/match.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/axelrod/match.py b/axelrod/match.py index d4d043b9a..a85cc5b4a 100644 --- a/axelrod/match.py +++ b/axelrod/match.py @@ -180,10 +180,9 @@ def play(self): if self.reset: p.reset() p.set_match_attributes(**self.match_attributes) - # Generate a random seed for the player - # TODO: Seeds only for stochastic players - # if p.classifier["stochastic"]: - p.set_seed(self._random.random_seed_int()) + # Generate a random seed for the player, if stochastic + if Classifiers["stochastic"](p): + p.set_seed(self._random.random_seed_int()) result = [] for _ in range(turns): plays = self.simultaneous_play( From 950f1a526078d65a20cfae3c1ed98ddef203acd7 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Wed, 8 Jul 2020 18:30:29 -0700 Subject: [PATCH 062/121] Rewrite parts of strategy transformers to fix issues with classification and calling _random unecessarily --- axelrod/player.py | 27 +- axelrod/strategy_transformers.py | 110 +++++--- .../tests/unit/test_strategy_transformers.py | 241 +++++++++++++++++- 3 files changed, 332 insertions(+), 46 deletions(-) diff --git a/axelrod/player.py b/axelrod/player.py index e9466a0dd..de73e8c53 100644 --- a/axelrod/player.py +++ b/axelrod/player.py @@ -15,7 +15,16 @@ C, D = Action.C, Action.D -class Player(object): +class PostInitCaller(type): + """Metaclass to be able to handle post __init__ tasks.""" + def __call__(cls, *args, **kwargs): + obj = type.__call__(cls, *args, **kwargs) + obj._post_init() + obj._post_transform() + return obj + + +class Player(object, metaclass=PostInitCaller): """A class for a player in the tournament. This is an abstract base class, not intended to be used directly. @@ -23,6 +32,7 @@ class Player(object): name = "Player" classifier = {} # type: Dict[str, Any] + _reclassifiers = [] def __new__(cls, *args, **kwargs): """Caches arguments for Player cloning.""" @@ -50,11 +60,23 @@ def init_params(cls, *args, **kwargs): return boundargs.arguments def __init__(self): - """Initiates an empty history.""" + """Initial class setup.""" self._history = History() self.classifier = copy.deepcopy(self.classifier) self.set_match_attributes() + def _post_init(self): + """Post initialization tasks such as reclassifying the strategy.""" + pass + + def _post_transform(self): + """Overwrite this function if you need to do a post-transform, post-init + modification.""" + # Reclassify strategy post __init__, if needed. + for (reclassifier, args, kwargs) in self._reclassifiers: + self.classifier = reclassifier(self.classifier, *args, **kwargs) + pass + def __eq__(self, other): """ Test if two players are equal. @@ -124,7 +146,6 @@ def set_seed(self, seed): "Initializing player with seed from Axelrod module random number generator." " Results may not be seed reproducible.") self._seed = _module_random.random_seed_int() - # raise Exception() else: self._seed = seed self._random = RandomGenerator(seed=self._seed) diff --git a/axelrod/strategy_transformers.py b/axelrod/strategy_transformers.py index 609b26476..f0397fdba 100644 --- a/axelrod/strategy_transformers.py +++ b/axelrod/strategy_transformers.py @@ -5,9 +5,7 @@ See the various Meta strategies for another type of transformation. """ -import copy import inspect -from collections import Iterable from importlib import import_module from typing import Any @@ -84,30 +82,55 @@ def __call__(self, PlayerClass): del kwargs["name_prefix"] except KeyError: pass - try: - del kwargs["reclassifier"] - except KeyError: - pass + + # Since a strategy can be transformed multiple times, we need to build up an + # array of the reclassifiers. These will be dynamically added to the new class + # below. + reclassifiers = PlayerClass._reclassifiers.copy() + if reclassifier is not None: + reclassifiers.append((reclassifier, args, kwargs)) # Define the new strategy method, wrapping the existing method # with `strategy_wrapper` - def strategy(self, opponent): - if strategy_wrapper == dual_wrapper: - # dual_wrapper figures out strategy as if the Player had - # played the opposite actions of its current history. + # Here we build up the strategy function in parts to avoid unnecessary repeated + # auxiliary function calls. For example, is_strategy_static uses inspect + # which can be slow, so we don't want to call it more than needed. + + # First handle the case where the strategy method is static. + if is_strategy_static(PlayerClass): + def inner_strategy(self, opponent): + return PlayerClass.strategy(opponent) + else: + def inner_strategy(self, opponent): + return PlayerClass.strategy(self, opponent) + + # For the dual wrapper, we flip the history before and after the transform. + if strategy_wrapper == dual_wrapper: + def dual_inner_strategy(self, opponent): + """The dual wrapper requires flipping the history. It may be more efficient + to use a custom History class that tracks a flipped history and swaps labels.""" self._history = self.history.flip_plays() - - if is_strategy_static(PlayerClass): - proposed_action = PlayerClass.strategy(opponent) - else: - proposed_action = PlayerClass.strategy(self, opponent) - - if strategy_wrapper == dual_wrapper: - # After dual_wrapper calls the strategy, it returns - # the Player to its original state. + proposed_action = inner_strategy(self, opponent) self._history = self.history.flip_plays() + return proposed_action + outer_strategy = dual_inner_strategy + # For the JossAnn transformer, we want to avoid calling the wrapped strategy, + # in the cases where it is unnecessary, to avoid affecting stochasticity. + elif strategy_wrapper == joss_ann_wrapper: + def joss_ann_inner_strategy(self, opponent): + if not self.classifier["stochastic"]: + proposed_action = C + else: + proposed_action = inner_strategy(self, opponent) + return proposed_action + outer_strategy = joss_ann_inner_strategy + else: + outer_strategy = inner_strategy + + # Apply the wrapper + def strategy(self, opponent): + proposed_action = outer_strategy(self, opponent) - # Apply the wrapper return strategy_wrapper( self, opponent, proposed_action, *args, **kwargs ) @@ -122,12 +145,6 @@ def strategy(self, opponent): # Modify the Player name (class variable inherited from Player) name = " ".join([name_prefix, PlayerClass.name]) - original_classifier = copy.deepcopy(PlayerClass.classifier) # Copy - if reclassifier is not None: - classifier = reclassifier(original_classifier, *args, **kwargs) - else: - classifier = original_classifier - # Define the new __repr__ method to add the wrapper arguments # at the end of the name def __repr__(self): @@ -185,9 +202,9 @@ def reduce_for_decorated_class(self_): "decorator": self, "__repr__": __repr__, "__module__": PlayerClass.__module__, - "classifier": classifier, "__doc__": PlayerClass.__doc__, "__reduce__": reduce_for_decorated_class, + "_reclassifiers": reclassifiers, }, ) @@ -231,7 +248,6 @@ class DecoratorReBuilder(object): def __call__( self, factory_args: tuple, args: tuple, kwargs: dict, instance_name_prefix: str ) -> Any: - decorator_class = StrategyTransformerFactory(*factory_args) kwargs["name_prefix"] = instance_name_prefix return decorator_class(*args, **kwargs) @@ -340,6 +356,10 @@ def dual_wrapper(player, opponent: Player, proposed_action: Action) -> Action: def noisy_wrapper(player, opponent, action, noise=0.05): """Flips the player's actions with probability: `noise`.""" + if noise == 0: + return action + if noise == 1: + return action.flip() r = player._random.random() if r < noise: return action.flip() @@ -362,6 +382,10 @@ def forgiver_wrapper(player, opponent, action, p): """If a strategy wants to defect, flip to cooperate with the given probability.""" if action == D: + if p == 0: + return D + if p == 1: + return C return player._random.random_choice(p) return C @@ -606,7 +630,8 @@ def joss_ann_wrapper(player, opponent, proposed_action, probability): options = [C, D, proposed_action] # Avoid use of self._random if strategy is actually deterministic. - if 1 in probability: # If all probability given to one player + # if any(0 < x < 1 for x in probability) or not all(x == 0 for x in probability): + if 1 in probability: option = options[probability.index(1)] return option @@ -624,7 +649,7 @@ def jossann_reclassifier(original_classifier, probability): if probability in [(1, 0), (0, 1)]: original_classifier["stochastic"] = False - elif sum(probability) != 0: + else: original_classifier["stochastic"] = True return original_classifier @@ -655,8 +680,16 @@ def __call__(self, player, opponent, action, retaliations): return D +def retailiation_reclassifier(original_classifier, retaliations): + if retaliations > 0: + original_classifier["memory_depth"] = max( + retaliations, original_classifier["memory_depth"]) + return original_classifier + + RetaliationTransformer = StrategyTransformerFactory( - RetaliationWrapper(), name_prefix="Retaliating" + RetaliationWrapper(), name_prefix="Retaliating", + reclassifier=retailiation_reclassifier ) @@ -666,18 +699,19 @@ class RetaliationUntilApologyWrapper(object): def __call__(self, player, opponent, action): if len(player.history) == 0: - self.is_retaliating = False return action if opponent.history[-1] == D: - self.is_retaliating = True - if self.is_retaliating: - if opponent.history[-1] == C: - self.is_retaliating = False - return C return D return action +def rua_reclassifier(original_classifier): + original_classifier["memory_depth"] = max( + 1, original_classifier["memory_depth"]) + return original_classifier + + RetaliateUntilApologyTransformer = StrategyTransformerFactory( - RetaliationUntilApologyWrapper(), name_prefix="RUA" + RetaliationUntilApologyWrapper(), name_prefix="RUA", + reclassifier=rua_reclassifier ) diff --git a/axelrod/tests/unit/test_strategy_transformers.py b/axelrod/tests/unit/test_strategy_transformers.py index 808d7a694..433d8f4f4 100644 --- a/axelrod/tests/unit/test_strategy_transformers.py +++ b/axelrod/tests/unit/test_strategy_transformers.py @@ -1,8 +1,9 @@ import axelrod as axl from axelrod.strategy_transformers import * from axelrod.tests.strategies.test_cooperator import TestCooperator +from axelrod.tests.strategies.test_defector import TestDefector from axelrod.tests.strategies.test_titfortat import TestTitForTat -from axelrod.tests.strategies.test_player import TestMatch +from axelrod.tests.strategies.test_player import TestMatch, TestPlayer C, D = axl.Action.C, axl.Action.D @@ -499,6 +500,20 @@ def test_deterministic_match(self): self.assertFalse(axl.Classifiers["stochastic"](p1)) self.versus_test(p1, p2, [D] * 5, [C] * 5) + def test_deterministic_match_override(self): + """Tests the JossAnn transformer.""" + probability = (1, 0) + p1 = JossAnnTransformer(probability)(axl.Random)() + self.assertFalse(axl.Classifiers["stochastic"](p1)) + p2 = axl.Cooperator() + self.versus_test(p1, p2, [C] * 5, [C] * 5) + + probability = (0, 1) + p1 = JossAnnTransformer(probability)(axl.Random)() + self.assertFalse(axl.Classifiers["stochastic"](p1)) + p2 = axl.Cooperator() + self.versus_test(p1, p2, [D] * 5, [C] * 5) + def test_stochastic1(self): probability = (0.3, 0.3) p1 = JossAnnTransformer(probability)(axl.TitForTat)() @@ -532,7 +547,7 @@ def test_stochastic_classifiers(self): probability = (0, 0) p1 = JossAnnTransformer(probability)(axl.TitForTat) - self.assertFalse(axl.Classifiers["stochastic"](p1())) + self.assertTrue(axl.Classifiers["stochastic"](p1())) probability = (0, 0) p1 = JossAnnTransformer(probability)(axl.Random) @@ -669,12 +684,65 @@ def test_retaliation_until_apology_stochastic(self): self.versus_test(p1, p2, [C, C, D, D, C], [C, D, D, C, C], seed=35) -class TestRUAisTFT(TestTitForTat): - # This runs the 7 TFT tests when unittest is invoked +# Run the standard Player tests on some specifically transformed players + +class TestNullInitialTransformedCooperator(TestPlayer): + player = InitialTransformer([])(axl.Cooperator) + name = "Initial Cooperator: []" + expected_classifier = { + "memory_depth": 0, + "stochastic": False, + "makes_use_of": set(), + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +class TestInitialTransformedCooperator(TestPlayer): + player = InitialTransformer([D, D])(axl.Cooperator) + name = "Initial Cooperator: [D, D]" + expected_classifier = { + "memory_depth": 2, + "stochastic": False, + "makes_use_of": set(), + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +class TestFinalTransformedCooperator(TestPlayer): + player = FinalTransformer([D, D, D])(axl.Cooperator) + name = "Final Cooperator: [D, D, D]" + expected_classifier = { + "memory_depth": 3, + "stochastic": False, + "makes_use_of": {"length"}, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +class TestInitialFinalTransformedCooperator(TestPlayer): + player = InitialTransformer([D, D])(FinalTransformer([D, D, D])(axl.Cooperator)) + name = "Initial Final Cooperator: [D, D, D]: [D, D]" + expected_classifier = { + "memory_depth": 3, + "stochastic": False, + "makes_use_of": {"length"}, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +class TestRUACooperatorisTFT(TestTitForTat): player = RetaliateUntilApologyTransformer()(axl.Cooperator) name = "RUA Cooperator" expected_classifier = { - "memory_depth": 0, # really 1 + "memory_depth": 1, "stochastic": False, "makes_use_of": set(), "long_run_time": False, @@ -688,3 +756,166 @@ class TestFlipDefector(TestCooperator): # Test that FlipTransformer(Defector) == Cooperator name = "Flipped Defector" player = FlipTransformer()(axl.Defector) + expected_classifier = { + "memory_depth": 0, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +class TestNoisyNullCooperator(TestCooperator): + name = "Noisy Cooperator: 0" + player = NoisyTransformer(0)(axl.Cooperator) + expected_classifier = { + "memory_depth": 0, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +class TestFullNoisyCooperatorIsDefector(TestDefector): + name = "Noisy Cooperator: 1" + player = NoisyTransformer(1)(axl.Cooperator) + expected_classifier = { + "memory_depth": 0, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +class TestNullForgivingCooperator(TestDefector): + name = "Forgiving Defector: 0" + player = ForgiverTransformer(0)(axl.Defector) + expected_classifier = { + "memory_depth": 0, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +class TestFullForgivingCooperatorIsDefector(TestCooperator): + name = "Forgiving Defector: 1" + player = ForgiverTransformer(1)(axl.Defector) + expected_classifier = { + "memory_depth": 0, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +class TestMixed0(TestDefector): + name = "Mutated Defector: 0, " + player = MixedTransformer(0, axl.Cooperator)(axl.Defector) + expected_classifier = { + "memory_depth": 0, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +class TestMixed1(TestDefector): + name = "Mutated Cooperator: 1, " + player = MixedTransformer(1, axl.Defector)(axl.Cooperator) + expected_classifier = { + "memory_depth": 0, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +class TestIdentityDualTransformer(TestPlayer): + name = "Dual Cooperator" + player = IdentityTransformer()(DualTransformer()(axl.Cooperator)) + expected_classifier = { + "memory_depth": 0, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +class TestFlippedDualTransformer(TestPlayer): + name = "Flipped Dual Cooperator" + player = FlipTransformer()(DualTransformer()(axl.Cooperator)) + expected_classifier = { + "memory_depth": 0, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +class TestIdentityDualTransformer(TestPlayer): + name = "Dual Cooperator" + player = IdentityTransformer()(DualTransformer()(axl.Cooperator)) + expected_classifier = { + "memory_depth": 0, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +class TestDualJossAnn(TestPlayer): + name = "Dual Joss-Ann Alternator: (0.2, 0.3)" + player = DualTransformer()(JossAnnTransformer((0.2, 0.3))(axl.Alternator)) + expected_classifier = { + "memory_depth": 1, + "stochastic": True, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +class TestJossAnnDual(TestPlayer): + name = "Joss-Ann Dual Alternator: (0.2, 0.3)" + player = JossAnnTransformer((0.2, 0.3))(DualTransformer()(axl.Alternator)) + expected_classifier = { + "memory_depth": 1, + "stochastic": True, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } From d8b1c755423a395e4816c9b963d7610ef33a73da Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Wed, 8 Jul 2020 18:31:18 -0700 Subject: [PATCH 063/121] Fix flip_history bug with LimitedHistory --- axelrod/history.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/axelrod/history.py b/axelrod/history.py index 9e21b04c7..9c0ff2ef1 100644 --- a/axelrod/history.py +++ b/axelrod/history.py @@ -108,16 +108,21 @@ class LimitedHistory(History): depth. """ - def __init__(self, memory_depth): + def __init__(self, memory_depth, plays=None, coplays=None): """ Parameters ---------- memory_depth, int: length of history to retain """ - super().__init__() + super().__init__(plays=plays, coplays=coplays) self.memory_depth = memory_depth + def flip_plays(self): + """Creates a flipped plays history for use with DualTransformer.""" + flipped_plays = [action.flip() for action in self._plays] + return self.__class__(self.memory_depth, plays=flipped_plays, coplays=self._coplays) + def append(self, play, coplay): """Appends a new (play, coplay) pair an updates metadata for number of cooperations and defections, and the state distribution.""" From 6987307e1f343d5925a16e6c9bea11d18160738e Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Wed, 8 Jul 2020 18:32:09 -0700 Subject: [PATCH 064/121] Rework Random to avoid calls to _random --- axelrod/strategies/rand.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/axelrod/strategies/rand.py b/axelrod/strategies/rand.py index 5cfd9f0e8..9aed90466 100644 --- a/axelrod/strategies/rand.py +++ b/axelrod/strategies/rand.py @@ -41,14 +41,19 @@ def __init__(self, p: float = 0.5) -> None: """ super().__init__() self.p = p - if p in [0, 1]: + + def strategy(self, opponent: Player) -> Action: + return self._random.random_choice(self.p) + + def _post_init(self): + if self.p in [0, 1]: self.classifier["stochastic"] = False # Avoid calls to _random, if strategy is deterministic # by overwriting the strategy function. - if p == 0: - self.strategy = self.cooperate - if p == 1: + if self.p <= 0: self.strategy = self.defect + if self.p >= 1: + self.strategy = self.cooperate @classmethod def cooperate(cls, opponent: Player) -> Action: @@ -58,5 +63,3 @@ def cooperate(cls, opponent: Player) -> Action: def defect(cls, opponent: Player) -> Action: return D - def strategy(self, opponent: Player) -> Action: - return self._random.random_choice(self.p) From 5897fdbe55278ed7882b1bb9dcee3d4acefdc011 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Wed, 8 Jul 2020 18:32:50 -0700 Subject: [PATCH 065/121] Add more test for Random strategy --- axelrod/tests/strategies/test_rand.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/axelrod/tests/strategies/test_rand.py b/axelrod/tests/strategies/test_rand.py index 90562aaab..38066eb17 100644 --- a/axelrod/tests/strategies/test_rand.py +++ b/axelrod/tests/strategies/test_rand.py @@ -21,24 +21,24 @@ class TestRandom(TestPlayer): "manipulates_state": False, } - def test_strategy(self): + def test_deterministic(self): + actions = [(D, C), (D, C), (D, C)] + self.versus_test(axl.Cooperator(), expected_actions=actions, init_kwargs={"p": 0}) + + actions = [(C, C), (C, C), (C, C)] + self.versus_test(axl.Cooperator(), expected_actions=actions, init_kwargs={"p": 1}) + + def test_stochastic_behavior1(self): """Test that strategy is randomly picked (not affected by history).""" opponent = axl.MockPlayer() actions = [(C, C), (D, C), (D, C), (C, C)] self.versus_test(opponent, expected_actions=actions, seed=1) + def test_stochastic_behavior1(self): opponent = axl.MockPlayer() actions = [(D, C), (C, C), (D, C)] self.versus_test(opponent, expected_actions=actions, seed=2) - opponent = axl.MockPlayer() - actions = [(D, C), (D, C), (D, C)] - self.versus_test(opponent, expected_actions=actions, init_kwargs={"p": 0}) - - opponent = axl.MockPlayer() - actions = [(C, C), (C, C), (C, C)] - self.versus_test(opponent, expected_actions=actions, init_kwargs={"p": 1}) - def test_deterministic_classification(self): """Test classification when p is 0 or 1""" for p in [0, 1]: From 83b6db856e2cb6d3210bd1ddab1367c9babe402e Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Wed, 8 Jul 2020 18:33:57 -0700 Subject: [PATCH 066/121] Cleanup evolvable_player.py and propagate forward seeds --- axelrod/evolvable_player.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/axelrod/evolvable_player.py b/axelrod/evolvable_player.py index 20f835828..0fbbad9c4 100644 --- a/axelrod/evolvable_player.py +++ b/axelrod/evolvable_player.py @@ -17,12 +17,6 @@ def __init__(self, *args): super().__init__(*args) -# class SeedNullingError(Exception): -# """Error indicating that a required seed was not supplied.""" -# def __init__(self, *args): -# super().__init__(*args) - - class EvolvablePlayer(Player): """A class for a player that can evolve, for use in the Moran process or with reinforcement learning algorithms. @@ -35,13 +29,6 @@ class EvolvablePlayer(Player): # Parameter seed is actually required. def __init__(self, seed=None): - # if seed is None: - # raise SeedNotGivenError(seed) - # # Was the seed already set? - # if (not hasattr(self, "_seed")) or (hasattr(self, "_seed") and self._seed==None): - # raise SeedNotGivenError() - # raise SeedNullingError() - # else: super().__init__() self.set_seed(seed=seed) @@ -54,6 +41,9 @@ def create_new(self, **kwargs): """Creates a new variant with parameters overwritten by kwargs.""" 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 From fa2ca0ceb3c101e20664b928610924c8b5263e35 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Tue, 28 Jul 2020 20:08:02 -0700 Subject: [PATCH 067/121] Update seeds for TFT tests and add new tests --- axelrod/tests/strategies/test_titfortat.py | 111 ++++++++++++--------- 1 file changed, 62 insertions(+), 49 deletions(-) diff --git a/axelrod/tests/strategies/test_titfortat.py b/axelrod/tests/strategies/test_titfortat.py index 7a2e95948..39fe19c3e 100644 --- a/axelrod/tests/strategies/test_titfortat.py +++ b/axelrod/tests/strategies/test_titfortat.py @@ -29,17 +29,10 @@ class TestTitForTat(TestPlayer): "manipulates_state": False, } - def test_strategy(self): - # Play against opponents + def test_vs_alternator(self): actions = [(C, C), (C, D), (D, C), (C, D), (D, C)] self.versus_test(axl.Alternator(), expected_actions=actions) - actions = [(C, C), (C, C), (C, C), (C, C), (C, C)] - self.versus_test(axl.Cooperator(), expected_actions=actions) - - actions = [(C, D), (D, D), (D, D), (D, D), (D, D)] - self.versus_test(axl.Defector(), expected_actions=actions) - # This behaviour is independent of knowledge of the Match length actions = [(C, C), (C, D), (D, C), (C, D), (D, C)] self.versus_test( @@ -48,13 +41,24 @@ def test_strategy(self): match_attributes={"length": float("inf")}, ) + def test_vs_cooperator(self): + actions = [(C, C), (C, C), (C, C), (C, C), (C, C)] + self.versus_test(axl.Cooperator(), expected_actions=actions) + + def test_vs_defector(self): + actions = [(C, D), (D, D), (D, D), (D, D), (D, D)] + self.versus_test(axl.Defector(), expected_actions=actions) + + def test_vs_random(self): # We can also test against random strategies actions = [(C, D), (D, C), (C, C), (C, D), (D, D)] - self.versus_test(axl.Random(), expected_actions=actions, seed=37) + self.versus_test(axl.Random(), expected_actions=actions, seed=17) + def test_vs_random2(self): actions = [(C, C), (C, C), (C, C), (C, C)] - self.versus_test(axl.Random(), expected_actions=actions, seed=1) + self.versus_test(axl.Random(), expected_actions=actions, seed=3) + def test_vs_mock_players(self): # If you would like to test against a sequence of moves you should use # a MockPlayer opponent = axl.MockPlayer(actions=[C, D]) @@ -733,21 +737,21 @@ def test_strategy_with_noise2(self): def test_strategy_with_noise3(self): # After noise: is contrite actions = list(zip([D, C], [C, D])) - self.versus_test(axl.Defector(), actions, turns=2, noise=0.5, seed=37, + self.versus_test(axl.Defector(), actions, turns=2, noise=0.5, seed=49, attrs={"_recorded_history": [C, C], "contrite": True}) def test_strategy_with_noise4(self): # Cooperates and no longer contrite actions = list(zip([D, C, C], [C, D, D])) - self.versus_test(axl.Defector(), actions, turns=3, noise=0.5, seed=151, + self.versus_test(axl.Defector(), actions, turns=3, noise=0.5, seed=49, attrs={"_recorded_history": [C, C, C], "contrite": False}) def test_strategy_with_noise5(self): # Defects and no longer contrite actions = list(zip([D, C, C, D], [C, D, D, D])) - self.versus_test(axl.Defector(), actions, turns=4, noise=0.5, seed=259, + self.versus_test(axl.Defector(), actions, turns=4, noise=0.5, seed=158, attrs={"_recorded_history": [C, C, C, D], "contrite": False}) @@ -947,42 +951,6 @@ class TestNTitsForMTats(TestPlayer): expected_class_classifier = copy.copy(expected_classifier) def test_strategy(self): - # TitForTat test_strategy - init_kwargs = {"N": 1, "M": 1} - actions = [(C, C), (C, D), (D, C), (C, D), (D, C)] - self.versus_test( - axl.Alternator(), expected_actions=actions, init_kwargs=init_kwargs - ) - actions = [(C, C), (C, C), (C, C), (C, C), (C, C)] - self.versus_test( - axl.Cooperator(), expected_actions=actions, init_kwargs=init_kwargs - ) - actions = [(C, D), (D, D), (D, D), (D, D), (D, D)] - self.versus_test( - axl.Defector(), expected_actions=actions, init_kwargs=init_kwargs - ) - actions = [(C, C), (C, D), (D, C), (C, D), (D, C)] - self.versus_test( - axl.Alternator(), - expected_actions=actions, - match_attributes={"length": float("inf")}, - init_kwargs=init_kwargs, - ) - actions = [(C, D), (D, C), (C, C), (C, D), (D, D)] - self.versus_test( - axl.Random(), expected_actions=actions, seed=37, init_kwargs=init_kwargs - ) - actions = [(C, D), (D, C), (C, D), (D, D)] - self.versus_test( - axl.Random(), expected_actions=actions, seed=2, init_kwargs=init_kwargs - ) - opponent = axl.MockPlayer(actions=[C, D]) - actions = [(C, C), (C, D), (D, C), (C, D)] - self.versus_test(opponent, expected_actions=actions, init_kwargs=init_kwargs) - opponent = axl.MockPlayer(actions=[C, C, D, D, C, D]) - actions = [(C, C), (C, C), (C, D), (D, D), (D, C), (C, D)] - self.versus_test(opponent, expected_actions=actions, init_kwargs=init_kwargs) - # TitFor2Tats test_strategy init_kwargs = {"N": 1, "M": 2} opponent = axl.MockPlayer(actions=[D, D, D, C, C]) @@ -1047,6 +1015,51 @@ def test_varying_memory_depth(self): self.assertEqual(axl.Classifiers["memory_depth"](self.player(5, 3)), 5) +class Test1TitsFor1TatsIsTFT(TestTitForTat): + """Tests that for N = 1 = M, all the TFT tests are passed.""" + name = "N Tit(s) For M Tat(s): 1, 1" + player = lambda x: axl.NTitsForMTats(1, 1) + expected_classifier = { + "memory_depth": 1, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +class Test1TitsFor2TatsIsTF2T(TestTitFor2Tats): + """Tests that for N = 1 = M, all the TFT tests are passed.""" + name = "N Tit(s) For M Tat(s): 1, 2" + player = lambda x: axl.NTitsForMTats(1, 2) + expected_classifier = { + "memory_depth": 2, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +class Test2TitsFor1TatsIsT2FT(TestTwoTitsForTat): + """Tests that for N = 1 = M, all the TFT tests are passed.""" + name = "N Tit(s) For M Tat(s): 2, 1" + player = lambda x: axl.NTitsForMTats(2, 1) + expected_classifier = { + "memory_depth": 2, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + class TestMichaelos(TestPlayer): """ Tests for the Michaelos strategy From 7b503acc716f36de9291f2401737353b826c62cf Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Tue, 28 Jul 2020 20:08:23 -0700 Subject: [PATCH 068/121] Fix fingerprint tests --- axelrod/tests/unit/test_fingerprint.py | 165 ++++++++++++------------- 1 file changed, 77 insertions(+), 88 deletions(-) diff --git a/axelrod/tests/unit/test_fingerprint.py b/axelrod/tests/unit/test_fingerprint.py index a82098e17..7a5ae1629 100644 --- a/axelrod/tests/unit/test_fingerprint.py +++ b/axelrod/tests/unit/test_fingerprint.py @@ -246,7 +246,7 @@ def test_plot_data(self): af = AshlockFingerprint(axl.Cooperator()) af.fingerprint(turns=5, repetitions=3, step=0.5, progress_bar=False, seed=0) - reshaped_data = np.array([[0.0, 0.0, 0.0], [1.4, 1.8, 1.2], [3.0, 3.0, 3.0]]) + reshaped_data = np.array([[0.0, 0.0, 0.0], [1., 2., 1.6], [3.0, 3.0, 3.0]]) plotted_data = af.plot().gca().images[0].get_array() np.testing.assert_allclose(plotted_data, reshaped_data) @@ -268,32 +268,33 @@ def test_plot_figure(self): def test_wsls_fingerprint(self): test_data = { - Point(x=0.0, y=0.0): 3.000, - Point(x=0.0, y=0.25): 1.740, - Point(x=0.0, y=0.5): 1.300, - Point(x=0.0, y=0.75): 0.900, - Point(x=0.0, y=1.0): 0.500, - Point(x=0.25, y=0.0): 3.000, - Point(x=0.25, y=0.25): 2.250, - Point(x=0.25, y=0.5): 1.990, - Point(x=0.25, y=0.75): 1.250, - Point(x=0.25, y=1.0): 0.670, - Point(x=0.5, y=0.0): 3.000, - Point(x=0.5, y=0.25): 2.700, - Point(x=0.5, y=0.5): 2.420, - Point(x=0.5, y=0.75): 1.700, - Point(x=0.5, y=1.0): 0.950, - Point(x=0.75, y=0.0): 3.000, - Point(x=0.75, y=0.25): 2.920, - Point(x=0.75, y=0.5): 3.000, - Point(x=0.75, y=0.75): 2.770, - Point(x=0.75, y=1.0): 1.100, - Point(x=1.0, y=0.0): 3.000, - Point(x=1.0, y=0.25): 4.800, - Point(x=1.0, y=0.5): 3.860, - Point(x=1.0, y=0.75): 4.380, - Point(x=1.0, y=1.0): 1.300, + Point(x=0.0, y=0.0): 3.0, + Point(x=0.0, y=0.25): 1.83, + Point(x=0.0, y=0.5): 1.12, + Point(x=0.0, y=0.75): 1.04, + Point(x=0.0, y=1.0): 0.5, + Point(x=0.25, y=0.0): 3.0, + Point(x=0.25, y=0.25): 2.12, + Point(x=0.25, y=0.5): 2.17, + Point(x=0.25, y=0.75): 1.33, + Point(x=0.25, y=1.0): 0.77, + Point(x=0.5, y=0.0): 3.0, + Point(x=0.5, y=0.25): 2.9299999999999997, + Point(x=0.5, y=0.5): 2.3600000000000003, + Point(x=0.5, y=0.75): 1.74, + Point(x=0.5, y=1.0): 1.05, + Point(x=0.75, y=0.0): 3.0, + Point(x=0.75, y=0.25): 2.52, + Point(x=0.75, y=0.5): 2.79, + Point(x=0.75, y=0.75): 2.41, + Point(x=0.75, y=1.0): 1.2, + Point(x=1.0, y=0.0): 3.0, + Point(x=1.0, y=0.25): 4.86, + Point(x=1.0, y=0.5): 4.36, + Point(x=1.0, y=0.75): 4.05, + Point(x=1.0, y=1.0): 1.3 } + af = axl.AshlockFingerprint(axl.WinStayLoseShift(), axl.TitForTat) data = af.fingerprint(turns=50, repetitions=2, step=0.25, progress_bar=False, seed=0) @@ -303,31 +304,31 @@ def test_wsls_fingerprint(self): def test_tft_fingerprint(self): test_data = { - Point(x=0.0, y=0.0): 3.000, - Point(x=0.0, y=0.25): 1.100, - Point(x=0.0, y=0.5): 1.070, - Point(x=0.0, y=0.75): 0.980, - Point(x=0.0, y=1.0): 0.980, - Point(x=0.25, y=0.0): 3.000, - Point(x=0.25, y=0.25): 2.250, - Point(x=0.25, y=0.5): 1.880, - Point(x=0.25, y=0.75): 1.640, - Point(x=0.25, y=1.0): 1.460, - Point(x=0.5, y=0.0): 3.000, - Point(x=0.5, y=0.25): 2.540, - Point(x=0.5, y=0.5): 2.330, - Point(x=0.5, y=0.75): 2.100, - Point(x=0.5, y=1.0): 1.830, - Point(x=0.75, y=0.0): 3.000, - Point(x=0.75, y=0.25): 2.780, - Point(x=0.75, y=0.5): 2.520, - Point(x=0.75, y=0.75): 2.250, - Point(x=0.75, y=1.0): 1.980, - Point(x=1.0, y=0.0): 3.000, - Point(x=1.0, y=0.25): 2.770, - Point(x=1.0, y=0.5): 2.460, - Point(x=1.0, y=0.75): 2.360, - Point(x=1.0, y=1.0): 2.180, + Point(x=0.0, y=0.0): 3.0, + Point(x=0.0, y=0.25): 1.21, + Point(x=0.0, y=0.5): 1.01, + Point(x=0.0, y=0.75): 1.04, + Point(x=0.0, y=1.0): 0.98, + Point(x=0.25, y=0.0): 3.0, + Point(x=0.25, y=0.25): 2.01, + Point(x=0.25, y=0.5): 2.05, + Point(x=0.25, y=0.75): 1.71, + Point(x=0.25, y=1.0): 1.52, + Point(x=0.5, y=0.0): 3.0, + Point(x=0.5, y=0.25): 2.6500000000000004, + Point(x=0.5, y=0.5): 2.36, + Point(x=0.5, y=0.75): 2.1100000000000003, + Point(x=0.5, y=1.0): 1.8900000000000001, + Point(x=0.75, y=0.0): 3.0, + Point(x=0.75, y=0.25): 2.57, + Point(x=0.75, y=0.5): 2.51, + Point(x=0.75, y=0.75): 2.13, + Point(x=0.75, y=1.0): 2.12, + Point(x=1.0, y=0.0): 3.0, + Point(x=1.0, y=0.25): 2.68, + Point(x=1.0, y=0.5): 2.51, + Point(x=1.0, y=0.75): 2.41, + Point(x=1.0, y=1.0): 2.18 } af = axl.AshlockFingerprint(axl.TitForTat(), axl.TitForTat) @@ -339,31 +340,31 @@ def test_tft_fingerprint(self): def test_majority_fingerprint(self): test_data = { - Point(x=0.0, y=0.0): 3.000, - Point(x=0.0, y=0.25): 1.630, - Point(x=0.0, y=0.5): 1.070, - Point(x=0.0, y=0.75): 0.980, - Point(x=0.0, y=1.0): 0.980, - Point(x=0.25, y=0.0): 3.000, - Point(x=0.25, y=0.25): 2.100, - Point(x=0.25, y=0.5): 1.840, - Point(x=0.25, y=0.75): 1.880, - Point(x=0.25, y=1.0): 1.700, - Point(x=0.5, y=0.0): 3.000, - Point(x=0.5, y=0.25): 2.310, - Point(x=0.5, y=0.5): 2.270, - Point(x=0.5, y=0.75): 2.670, - Point(x=0.5, y=1.0): 2.580, - Point(x=0.75, y=0.0): 3.000, - Point(x=0.75, y=0.25): 2.440, - Point(x=0.75, y=0.5): 1.880, - Point(x=0.75, y=0.75): 2.030, - Point(x=0.75, y=1.0): 2.540, - Point(x=1.0, y=0.0): 3.000, - Point(x=1.0, y=0.25): 2.370, - Point(x=1.0, y=0.5): 1.920, - Point(x=1.0, y=0.75): 2.070, - Point(x=1.0, y=1.0): 2.260, + Point(x=0.0, y=0.0): 3.0, + Point(x=0.0, y=0.25): 1.6, + Point(x=0.0, y=0.5): 1.01, + Point(x=0.0, y=0.75): 1.04, + Point(x=0.0, y=1.0): 0.98, + Point(x=0.25, y=0.0): 3.0, + Point(x=0.25, y=0.25): 2.12, + Point(x=0.25, y=0.5): 1.81, + Point(x=0.25, y=0.75): 2.06, + Point(x=0.25, y=1.0): 1.86, + Point(x=0.5, y=0.0): 3.0, + Point(x=0.5, y=0.25): 2.4299999999999997, + Point(x=0.5, y=0.5): 2.37, + Point(x=0.5, y=0.75): 2.74, + Point(x=0.5, y=1.0): 2.68, + Point(x=0.75, y=0.0): 3.0, + Point(x=0.75, y=0.25): 2.4299999999999997, + Point(x=0.75, y=0.5): 1.8399999999999999, + Point(x=0.75, y=0.75): 2.34, + Point(x=0.75, y=1.0): 2.46, + Point(x=1.0, y=0.0): 3.0, + Point(x=1.0, y=0.25): 2.12, + Point(x=1.0, y=0.5): 1.8599999999999999, + Point(x=1.0, y=0.75): 2.0300000000000002, + Point(x=1.0, y=1.0): 2.26 } af = axl.AshlockFingerprint(axl.GoByMajority, axl.TitForTat) @@ -381,18 +382,6 @@ def test_pair_fingerprints(self, strategy_pair): with any two given strategies or instances """ strategy, probe = strategy_pair - af = AshlockFingerprint(strategy, probe) - data = af.fingerprint(turns=2, repetitions=2, step=0.5, progress_bar=False) - self.assertIsInstance(data, dict) - - af = AshlockFingerprint(strategy(), probe) - data = af.fingerprint(turns=2, repetitions=2, step=0.5, progress_bar=False) - self.assertIsInstance(data, dict) - - af = AshlockFingerprint(strategy, probe()) - data = af.fingerprint(turns=2, repetitions=2, step=0.5, progress_bar=False) - self.assertIsInstance(data, dict) - af = AshlockFingerprint(strategy(), probe()) data = af.fingerprint(turns=2, repetitions=2, step=0.5, progress_bar=False) self.assertIsInstance(data, dict) From a8e6469b02d52ae2f02ca2f666f5391ea8185961 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Tue, 28 Jul 2020 20:54:24 -0700 Subject: [PATCH 069/121] Update and optimize HMM --- axelrod/strategies/hmm.py | 42 ++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/axelrod/strategies/hmm.py b/axelrod/strategies/hmm.py index b4ecb59a4..5c66b1536 100644 --- a/axelrod/strategies/hmm.py +++ b/axelrod/strategies/hmm.py @@ -71,6 +71,20 @@ def __init__( self.transitions_D = transitions_D self.emission_probabilities = emission_probabilities self.state = initial_state + self._cache_C = dict() + self._cache_D = dict() + self._cache_deterministic_transitions() + + def _cache_deterministic_transitions(self): + """Cache deterministic transitions to avoid unnecessary random draws.""" + # If 1 is in the transition vector, it's deterministic. Just pick it out. + # By caching we avoid repeated searches. + for state in range(len(self.transitions_C)): + if 1 in self.transitions_C[state]: + self._cache_C[state] = self.transitions_C[state].index(1) + for state in range(len(self.transitions_D)): + if 1 in self.transitions_D[state]: + self._cache_D[state] = self.transitions_D[state].index(1) def is_well_formed(self) -> bool: """ @@ -109,22 +123,26 @@ def move(self, opponent_action: Action) -> Action: opponent_action: Axelrod.Action The opponent's last action. """ - num_states = len(self.emission_probabilities) + # Choose next state. if opponent_action == C: - if 1 in self.transitions_C[self.state]: - next_state = [self.transitions_C[self.state].index(1)] - else: - next_state = self._random.choice(num_states, 1, p=self.transitions_C[self.state]) + try: + next_state = self._cache_C[self.state] + except KeyError: + num_states = len(self.emission_probabilities) + next_state = self._random.choice(num_states, 1, p=self.transitions_C[self.state])[0] else: - if 1 in self.transitions_D[self.state]: - next_state = [self.transitions_D[self.state].index(1)] - else: - next_state = self._random.choice(num_states, 1, p=self.transitions_D[self.state]) - self.state = next_state[0] + try: + next_state = self._cache_D[self.state] + except KeyError: + num_states = len(self.emission_probabilities) + next_state = self._random.choice(num_states, 1, p=self.transitions_D[self.state])[0] + + self.state = next_state + # Choose action to emit. p = self.emission_probabilities[self.state] - if p == 1: - return D if p == 0: + return D + if p == 1: return C action = self._random.random_choice(p) return action From b9a0d30f2172c5558cb63e294e9b954ba4f59801 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Tue, 28 Jul 2020 20:54:52 -0700 Subject: [PATCH 070/121] Update HMM tests --- axelrod/tests/strategies/test_hmm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/axelrod/tests/strategies/test_hmm.py b/axelrod/tests/strategies/test_hmm.py index 07aef7270..0741e0b3e 100644 --- a/axelrod/tests/strategies/test_hmm.py +++ b/axelrod/tests/strategies/test_hmm.py @@ -199,12 +199,12 @@ def test_strategy(self): class TestEvolvedHMM5vsCooperator(TestMatch): def test_rounds(self): - self.versus_test(axl.EvolvedHMM5(), axl.Cooperator(), [C] * 5, [C] * 5) + self.versus_test(axl.EvolvedHMM5(), axl.Cooperator(), [C] * 5, [C] * 5, seed=3) class TestEvolvedHMM5vsDefector(TestMatch): def test_rounds(self): - self.versus_test(axl.EvolvedHMM5(), axl.Defector(), [C, C, D], [D, D, D]) + self.versus_test(axl.EvolvedHMM5(), axl.Defector(), [C, C, D], [D, D, D], seed=5) class TestEvolvableHMMPlayer(unittest.TestCase): From fde9e4ff3e326b2ca3352ffc201ac381708b8ab8 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Tue, 28 Jul 2020 20:55:27 -0700 Subject: [PATCH 071/121] Update a Meta player test --- axelrod/tests/strategies/test_meta.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axelrod/tests/strategies/test_meta.py b/axelrod/tests/strategies/test_meta.py index c69bcbd6b..8351ef080 100644 --- a/axelrod/tests/strategies/test_meta.py +++ b/axelrod/tests/strategies/test_meta.py @@ -621,7 +621,7 @@ class TestNMWEStochastic(TestMetaPlayer): def test_strategy(self): actions = [(C, C), (C, D), (D, C), (C, D), (D, C)] - self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=20) + self.versus_test(opponent=axl.Alternator(), expected_actions=actions, seed=16) class TestNMWEFiniteMemory(TestMetaPlayer): From dc1d867f63bb383d855614e901c578005302bc03 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Tue, 28 Jul 2020 21:12:09 -0700 Subject: [PATCH 072/121] Update seeds for strategy transformer tests --- axelrod/tests/unit/test_strategy_transformers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/axelrod/tests/unit/test_strategy_transformers.py b/axelrod/tests/unit/test_strategy_transformers.py index 433d8f4f4..fdae5483f 100644 --- a/axelrod/tests/unit/test_strategy_transformers.py +++ b/axelrod/tests/unit/test_strategy_transformers.py @@ -624,7 +624,7 @@ def test_noisy_transformer(self): p1 = axl.Cooperator() p2 = NoisyTransformer(0.5)(axl.Cooperator)() self.assertTrue(axl.Classifiers["stochastic"](p2)) - self.versus_test(p1, p2, [C] * 10, [C, C, C, C, C, C, D, D, C, C], seed=344) + self.versus_test(p1, p2, [C] * 10, [D, C, C, D, C, D, C, D, D, C], seed=1) def test_noisy_transformation_stochastic(self): """Depending on the value of the noise parameter, the strategy may become stochastic @@ -681,7 +681,7 @@ def test_retaliation_until_apology_stochastic(self): TFT = RetaliateUntilApologyTransformer()(axl.Cooperator) p1 = TFT() p2 = axl.Random() - self.versus_test(p1, p2, [C, C, D, D, C], [C, D, D, C, C], seed=35) + self.versus_test(p1, p2, [C, C, D, D, C], [C, D, D, C, D], seed=1) # Run the standard Player tests on some specifically transformed players From c4a7dace766ead7ced6833ea3ebd859f74f70bcf Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Tue, 28 Jul 2020 22:02:40 -0700 Subject: [PATCH 073/121] Update Moran process to use propagate random seeds --- axelrod/moran.py | 48 ++++++++++------ axelrod/tests/unit/test_moran.py | 99 ++++++++++++++++++-------------- 2 files changed, 87 insertions(+), 60 deletions(-) diff --git a/axelrod/moran.py b/axelrod/moran.py index bf0f92768..3c9876c40 100644 --- a/axelrod/moran.py +++ b/axelrod/moran.py @@ -89,20 +89,6 @@ def __init__( seed: int A random seed for reproducibility """ - self.turns = turns - self.prob_end = prob_end - self.game = game - self.noise = noise - self.initial_players = players # save initial population - self.players = [] # type: List - self.populations = [] # type: List - self.set_players() - self.score_history = [] # type: List - self.winning_strategy_name = None # type: Optional[str] - self.mutation_rate = mutation_rate - self.stop_on_fixation = stop_on_fixation - self._random = RandomGenerator(seed=seed) - self._bulk_random = BulkRandomGenerator(self._random.random_seed_int()) m = mutation_method.lower() if m in ["atomic", "transition"]: self.mutation_method = m @@ -117,6 +103,20 @@ def __init__( self.deterministic_cache = deterministic_cache else: self.deterministic_cache = DeterministicCache() + self.turns = turns + self.prob_end = prob_end + self.game = game + self.noise = noise + self.initial_players = players # save initial population + self.players = [] # type: List + self.populations = [] # type: List + self.score_history = [] # type: List + self.winning_strategy_name = None # type: Optional[str] + self.mutation_rate = mutation_rate + self.stop_on_fixation = stop_on_fixation + self._random = RandomGenerator(seed=seed) + self._bulk_random = BulkRandomGenerator(self._random.random_seed_int()) + self.set_players() # Build the set of mutation targets # Determine the number of unique types (players) keys = set([str(p) for p in players]) @@ -150,11 +150,17 @@ def __init__( self.fixated = self.fixation_check() def set_players(self) -> None: - """Copy the initial players into the first population.""" + """Copy the initial players into the first population, setting seeds as needed.""" self.players = [] for player in self.initial_players: - player.reset() - self.players.append(player) + if (self.mutation_method == "atomic") and issubclass(player.__class__, EvolvablePlayer): + # For reproducibility, we generate random seeds for evolvable players. + seed = next(self._bulk_random) + new_player = player.create_new(seed=seed) + self.players.append(new_player) + else: + player.reset() + self.players.append(player) self.populations = [self.population_distribution()] def fitness_proportionate_selection(self, @@ -501,6 +507,14 @@ def __init__( ) self.cached_outcomes = cached_outcomes + def set_players(self) -> None: + """Copy the initial players into the first population.""" + self.players = [] + for player in self.initial_players: + player.reset() + self.players.append(player) + self.populations = [self.population_distribution()] + def score_all(self) -> List: """Plays the next round of the process. Every player is paired up against every other player and the total scores are obtained from the diff --git a/axelrod/tests/unit/test_moran.py b/axelrod/tests/unit/test_moran.py index b9ea897bf..8018683c6 100644 --- a/axelrod/tests/unit/test_moran.py +++ b/axelrod/tests/unit/test_moran.py @@ -65,29 +65,29 @@ def test_mutate(self): players = axl.Cooperator(), axl.Defector(), axl.TitForTat() mp = MoranProcess(players, mutation_rate=0.5, seed=0) self.assertEqual(mp.mutate(0), players[0]) - mp = MoranProcess(players, mutation_rate=0.5, seed=1) + mp = MoranProcess(players, mutation_rate=0.5, seed=2) self.assertEqual(mp.mutate(0), players[2]) - mp = MoranProcess(players, mutation_rate=0.5, seed=3) + mp = MoranProcess(players, mutation_rate=0.5, seed=7) self.assertEqual(mp.mutate(0), players[1]) def test_death_in_db(self): players = axl.Cooperator(), axl.Defector(), axl.TitForTat() mp = MoranProcess(players, mutation_rate=0.5, mode="db", seed=1) + self.assertEqual(mp.death(), 2) + self.assertEqual(mp.dead, 2) + mp = MoranProcess(players, mutation_rate=0.5, mode="db", seed=2) self.assertEqual(mp.death(), 0) self.assertEqual(mp.dead, 0) - mp = MoranProcess(players, mutation_rate=0.5, mode="db", seed=2) + mp = MoranProcess(players, mutation_rate=0.5, mode="db", seed=9) self.assertEqual(mp.death(), 1) self.assertEqual(mp.dead, 1) - mp = MoranProcess(players, mutation_rate=0.5, mode="db", seed=5) - self.assertEqual(mp.death(), 2) - self.assertEqual(mp.dead, 2) def test_death_in_bd(self): players = axl.Cooperator(), axl.Defector(), axl.TitForTat() edges = [(0, 1), (2, 0), (1, 2)] graph = axl.graph.Graph(edges, directed=True) mp = MoranProcess(players, mode="bd", interaction_graph=graph, seed=1) - self.assertEqual(mp.death(0), 0) + self.assertEqual(mp.death(0), 1) mp = MoranProcess(players, mode="bd", interaction_graph=graph, seed=2) self.assertEqual(mp.death(0), 1) mp = MoranProcess(players, mode="bd", interaction_graph=graph, seed=3) @@ -96,12 +96,12 @@ def test_death_in_bd(self): def test_birth_in_db(self): players = axl.Cooperator(), axl.Defector(), axl.TitForTat() mp = MoranProcess(players, mode="db", seed=1) - self.assertEqual(mp.death(), 0) + self.assertEqual(mp.death(), 2) self.assertEqual(mp.birth(0), 2) def test_birth_in_bd(self): players = axl.Cooperator(), axl.Defector(), axl.TitForTat() - mp = MoranProcess(players, mode="bd", seed=1) + mp = MoranProcess(players, mode="bd", seed=2) self.assertEqual(mp.birth(), 0) def test_fixation_check(self): @@ -132,8 +132,8 @@ def test_fps(self): players = axl.Cooperator(), axl.Defector() mp = MoranProcess(players, seed=1) self.assertEqual(mp.fitness_proportionate_selection([0, 0, 1]), 2) - self.assertEqual(mp.fitness_proportionate_selection([1, 1, 1]), 0) self.assertEqual(mp.fitness_proportionate_selection([1, 1, 1]), 2) + self.assertEqual(mp.fitness_proportionate_selection([1, 1, 1]), 0) def test_exit_condition(self): p1, p2 = axl.Cooperator(), axl.Cooperator() @@ -163,8 +163,8 @@ def test_two_players(self): p1, p2 = axl.Cooperator(), axl.Defector() mp = MoranProcess((p1, p2), seed=99) populations = mp.play() - self.assertEqual(len(mp), 5) - self.assertEqual(len(populations), 5) + self.assertEqual(len(mp), 2) + self.assertEqual(len(populations), 2) self.assertEqual(populations, mp.populations) self.assertEqual(mp.winning_strategy_name, str(p2)) @@ -172,8 +172,8 @@ def test_two_prob_end(self): p1, p2 = axl.Random(), axl.TitForTat() mp = MoranProcess((p1, p2), prob_end=0.5, seed=10) populations = mp.play() - self.assertEqual(len(mp), 4) - self.assertEqual(len(populations), 4) + self.assertEqual(len(mp), 2) + self.assertEqual(len(populations), 2) self.assertEqual(populations, mp.populations) self.assertEqual(mp.winning_strategy_name, str(p1)) @@ -183,7 +183,7 @@ def test_different_game(self): game = axl.Game(r=4, p=2, s=1, t=6) mp = MoranProcess((p1, p2), turns=5, game=game, seed=88) populations = mp.play() - self.assertEqual(mp.winning_strategy_name, str(p1)) + self.assertEqual(mp.winning_strategy_name, str(p2)) def test_death_birth(self): """Two player death-birth should fixate after one round.""" @@ -221,7 +221,7 @@ def test_two_random_players(self): self.assertEqual(len(mp), 2) self.assertEqual(len(populations), 2) self.assertEqual(populations, mp.populations) - self.assertEqual(mp.winning_strategy_name, str(p2)) + self.assertEqual(mp.winning_strategy_name, str(p1)) def test_two_players_with_mutation(self): p1, p2 = axl.Cooperator(), axl.Defector() @@ -282,8 +282,8 @@ def test_four_players(self): players.append(axl.Defector()) mp = MoranProcess(players, seed=29) populations = mp.play() - self.assertEqual(len(mp), 9) - self.assertEqual(len(populations), 9) + self.assertEqual(len(mp), 8) + self.assertEqual(len(populations), 8) self.assertEqual(populations, mp.populations) self.assertEqual(mp.winning_strategy_name, str(axl.Defector())) @@ -304,8 +304,8 @@ def test_reset(self): p1, p2 = axl.Cooperator(), axl.Defector() mp = MoranProcess((p1, p2), seed=45) mp.play() - self.assertEqual(len(mp), 4) - self.assertEqual(len(mp.score_history), 3) + self.assertEqual(len(mp), 2) + self.assertEqual(len(mp.score_history), 1) mp.reset() self.assertEqual(len(mp), 1) self.assertEqual(mp.winning_strategy_name, None) @@ -329,7 +329,7 @@ def test_constant_fitness_case(self): winners.append(mp.winning_strategy_name) mp.reset() winners = Counter(winners) - self.assertEqual(winners["Defector"], 88) + self.assertEqual(winners["Defector"], 86) def test_cache(self): p1, p2 = axl.Cooperator(), axl.Defector() @@ -337,7 +337,7 @@ def test_cache(self): mp.play() self.assertEqual(len(mp.deterministic_cache), 1) - # Check that can pass a pre built cache + # Check that can pass a pre-built cache cache = axl.DeterministicCache() mp = axl.MoranProcess((p1, p2), deterministic_cache=cache) self.assertEqual(cache, mp.deterministic_cache) @@ -356,11 +356,11 @@ def test_population_plot(self): fig, axarr = plt.subplots(2, 2) ax = axarr[1, 0] mp.populations_plot(ax=ax) - self.assertEqual(ax.get_xlim(), (-0.8, 16.8)) + self.assertEqual(ax.get_xlim(), (-0.7000000000000001, 14.7)) self.assertEqual(ax.get_ylim(), (0, 5.25)) # Run without a given axis ax = mp.populations_plot() - self.assertEqual(ax.get_xlim(), (-0.8, 16.8)) + self.assertEqual(ax.get_xlim(), (-0.7000000000000001, 14.7)) self.assertEqual(ax.get_ylim(), (0, 5.25)) def test_cooperator_can_win_with_fitness_transformation(self): @@ -374,36 +374,42 @@ def test_cooperator_can_win_with_fitness_transformation(self): fitness_transformation = lambda score: 1 - w + w * score mp = MoranProcess( players, turns=10, fitness_transformation=fitness_transformation, - seed=689 + seed=3419 ) populations = mp.play() self.assertEqual(mp.winning_strategy_name, "Cooperator") def test_atomic_mutation_fsm(self): - players = [axl.EvolvableFSMPlayer(num_states=2, initial_state=1, initial_action=C) + players = [axl.EvolvableFSMPlayer(num_states=2, initial_state=1, initial_action=C, + seed=4) for _ in range(5)] mp = MoranProcess(players, turns=10, mutation_method="atomic", seed=12) - population = mp.play() + rounds = 10 + for _ in range(rounds): + next(mp) self.assertEqual( - mp.winning_strategy_name, - 'EvolvableFSMPlayer: ((0, C, 1, D), (0, D, 1, C), (1, C, 0, D), (1, D, 1, C)), 1, C, 2, 0.1') - self.assertEqual(len(mp.populations), 31) - self.assertTrue(mp.fixated) + list(sorted(mp.populations[-1].items()))[0][0], + 'EvolvableFSMPlayer: ((0, C, 0, C), (0, D, 1, C), (1, C, 1, D), (1, D, 1, D)), 0, D, 2, 0.1, 2240802643') + self.assertEqual(len(mp.populations), 11) + self.assertFalse(mp.fixated) def test_atomic_mutation_cycler(self): cycle_length = 5 - players = [axl.EvolvableCycler(cycle_length=cycle_length) + players = [axl.EvolvableCycler(cycle_length=cycle_length, seed=4) for _ in range(5)] mp = MoranProcess(players, turns=10, mutation_method="atomic", seed=10) - population = mp.play() - self.assertEqual(mp.winning_strategy_name, 'EvolvableCycler: CDCDD, 5, 0.2, 1') - self.assertEqual(len(mp.populations), 19) - self.assertTrue(mp.fixated) + rounds = 10 + for _ in range(rounds): + next(mp) + self.assertEqual( + list(mp.populations[-1].items())[0], + ('EvolvableCycler: CCDDD, 5, 0.2, 1, 1164244177', 1)) + self.assertEqual(len(mp.populations), 11) + self.assertFalse(mp.fixated) def test_mutation_method_exceptions(self): - cycle_length = 5 - players = [axl.EvolvableCycler(cycle_length=cycle_length) + players = [axl.EvolvableCycler(cycle_length=cycle_length, seed=4) for _ in range(5)] with self.assertRaises(ValueError): MoranProcess(players, turns=10, mutation_method="random", seed=10) @@ -423,7 +429,12 @@ def test_complete(self): seeds = range(0, 5) players = [] N = 6 - graph = axl.graph.complete_graph(N) + interaction_graph = axl.graph.complete_graph(N, loops=False) + reproduction_graph = axl.graph.Graph( + interaction_graph.edges, directed=interaction_graph.directed + ) + reproduction_graph.add_loops() + for _ in range(N // 2): players.append(axl.Cooperator()) players.append(axl.Defector()) @@ -431,7 +442,9 @@ def test_complete(self): mp = MoranProcess(players, seed=seed) mp.play() winner = mp.winning_strategy_name - mp = MoranProcess(players, interaction_graph=graph, seed=seed) + mp = MoranProcess(players, interaction_graph=interaction_graph, + reproduction_graph=reproduction_graph, + seed=seed) mp.play() winner2 = mp.winning_strategy_name self.assertEqual(winner, winner2) @@ -439,7 +452,7 @@ def test_complete(self): def test_cycle(self): """A cycle should sometimes produce different results vs. the default case.""" - seeds = [(1, True), (10, False)] + seeds = [(1, True), (3, False)] players = [] N = 6 graph = axl.graph.cycle(N) @@ -459,7 +472,7 @@ def test_cycle(self): def test_asymmetry(self): """Asymmetry in interaction and reproduction should sometimes produce different results.""" - seeds = [(1, True), (20, False)] + seeds = [(1, True), (5, False)] players = [] N = 6 graph1 = axl.graph.cycle(N) @@ -486,7 +499,7 @@ def test_asymmetry(self): def test_cycle_death_birth(self): """Test that death-birth can have different outcomes in the graph case.""" - seeds = [(1, True), (6, False)] + seeds = [(1, True), (3, False)] players = [] N = 6 graph = axl.graph.cycle(N) From a2d28262354060f5c9fb8b070f286918555742dd Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Tue, 28 Jul 2020 22:03:55 -0700 Subject: [PATCH 074/121] Remove defunct tests in test_tournament --- axelrod/tests/unit/test_tournament.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/axelrod/tests/unit/test_tournament.py b/axelrod/tests/unit/test_tournament.py index 86a822d4f..8d5b7afbc 100644 --- a/axelrod/tests/unit/test_tournament.py +++ b/axelrod/tests/unit/test_tournament.py @@ -681,20 +681,6 @@ def make_chunk_generator(): # Check that matches no longer exist self.assertEqual((len(list(chunk_generator))), 0) - def test_match_cache_is_used(self): - """ - Create two Random players that are classified as deterministic. - As they are deterministic the cache will be used. - """ - FakeRandom = axl.Random - FakeRandom.classifier["stochastic"] = False - p1 = FakeRandom() - p2 = FakeRandom() - tournament = axl.Tournament((p1, p2), turns=5, repetitions=2) - results = tournament.play(progress_bar=False) - for player_scores in results.scores: - self.assertEqual(player_scores[0], player_scores[1]) - def test_write_interactions(self): tournament = axl.Tournament( name=self.test_name, From 5ec9b7032e6bf5d70327c3dcd356be09a1347079 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Tue, 28 Jul 2020 22:19:52 -0700 Subject: [PATCH 075/121] Update fingerpring docs test --- docs/tutorials/further_topics/fingerprinting.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/further_topics/fingerprinting.rst b/docs/tutorials/further_topics/fingerprinting.rst index 8493b4915..4370c02de 100644 --- a/docs/tutorials/further_topics/fingerprinting.rst +++ b/docs/tutorials/further_topics/fingerprinting.rst @@ -76,7 +76,7 @@ This allows for the fingerprinting of parametrized strategies:: >>> data {... >>> data[(0, 0)] - 4.1 + 3.75 Transitive Fingerprint ----------------------- From c1965452fa6fceb750cd3eca91d43c5536cda9b8 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Tue, 28 Jul 2020 22:21:38 -0700 Subject: [PATCH 076/121] Update docstrings in property.py --- axelrod/tests/property.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/axelrod/tests/property.py b/axelrod/tests/property.py index d1a6bc104..b53edb7c1 100644 --- a/axelrod/tests/property.py +++ b/axelrod/tests/property.py @@ -16,6 +16,8 @@ def strategy_lists( Parameters ---------- + strategies : List(axl.Player) + The set of strategies to draw from min_size : integer The minimum number of strategies to include max_size : integer @@ -82,6 +84,8 @@ def tournaments( Parameters ---------- + strategies : List(axl.Player) + The set of strategies to draw from min_size : integer The minimum number of strategies to include max_size : integer @@ -93,6 +97,8 @@ def tournaments( min_noise : float The minimum noise value min_noise : float + The minimum noise value + max_noise : float The maximum noise value min_repetitions : integer The minimum number of repetitions @@ -130,6 +136,8 @@ def prob_end_tournaments( Parameters ---------- + strategies : List(axl.Player) + The set of strategies to draw from min_size : integer The minimum number of strategies to include max_size : integer @@ -181,6 +189,8 @@ def spatial_tournaments( Parameters ---------- + strategies : List(axl.Player) + The set of strategies to draw from min_size : integer The minimum number of strategies to include max_size : integer @@ -248,6 +258,8 @@ def prob_end_spatial_tournaments( Parameters ---------- + strategies : List(axl.Player) + The set of strategies to draw from min_size : integer The minimum number of strategies to include max_size : integer From 50a0735c4619f2087a18f4e7176f5aa6c02b93ab Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Tue, 28 Jul 2020 22:23:09 -0700 Subject: [PATCH 077/121] Have match watcher skip transformed players, which don't currently serialize well --- axelrod/yaml.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/axelrod/yaml.py b/axelrod/yaml.py index 593e09f10..262fdd48f 100644 --- a/axelrod/yaml.py +++ b/axelrod/yaml.py @@ -86,8 +86,13 @@ def wrapper(*args, **kwargs): player_attributes=player_attributes) ) stream = open(filename, 'a') - stream.write("---\n") - yaml.dump(asdict(test_config), stream) + try: + stream.write("---\n") + yaml.dump(asdict(test_config), stream) + except TypeError: + # Some players, like transformed players, don't serialize + # well + pass stream.close() return func(*args, **kwargs) return wrapper @@ -96,7 +101,14 @@ def wrapper(*args, **kwargs): def load_matches(): stream = open(filename, 'r') matches = yaml.load_all(stream, Loader=yaml.Loader) - return [from_dict(data_class=TestMatchConfig, data=match, config=Config( - type_hooks={Tuple[Action, ...]: str_to_actions})) for match in matches] + deserialized = [] + for match in matches: + try: + m = from_dict(data_class=TestMatchConfig, data=match, config=Config( + type_hooks={Tuple[Action, ...]: str_to_actions})) + deserialized.append(m) + except TypeError: + continue + return deserialized From b1562308b696a878796d9bfbfede46bef9cc8cd1 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Tue, 28 Jul 2020 22:26:45 -0700 Subject: [PATCH 078/121] Cleanup eigen.py --- axelrod/eigen.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/axelrod/eigen.py b/axelrod/eigen.py index f7b6670f5..736527650 100644 --- a/axelrod/eigen.py +++ b/axelrod/eigen.py @@ -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. @@ -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 ------ @@ -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. @@ -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: From b396f0df0b54f7ff481758d94271e4e25d367162 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Tue, 28 Jul 2020 23:26:39 -0700 Subject: [PATCH 079/121] Update Axelrod First Tournament documentation tests with new seeds --- .../running_axelrods_first_tournament.rst | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/docs/tutorials/getting_started/running_axelrods_first_tournament.rst b/docs/tutorials/getting_started/running_axelrods_first_tournament.rst index 56f67c5cf..7c961591f 100644 --- a/docs/tutorials/getting_started/running_axelrods_first_tournament.rst +++ b/docs/tutorials/getting_started/running_axelrods_first_tournament.rst @@ -59,19 +59,22 @@ The results object contains the ranked names:: First by Grofman First by Shubik Tit For Tat - First by Tideman and Chieruzzi: (D, D) First by Nydegger + First by Tideman and Chieruzzi: (D, D) Grudger First by Davis: 10 First by Graaskamp: 0.05 First by Downing First by Feld: 1.0, 0.5, 200 - Random: 0.5 First by Tullock First by Joss: 0.9 First by Anonymous + Random: 0.5 + +We see that `TitForTat` does not in fact win this tournament. `TitForTat` typically does not +win, possibly because our implementations differ from the original strategies as their code +is not available. -We see that `TitForTat` does not in fact win this tournament. We can plot the reported rank (from [Axelrod1980]_) versus the reproduced one:: >>> import matplotlib.pyplot as plt @@ -147,13 +150,13 @@ Other outcomes -------------- If we run the tournament with other seeds, the results are different. For -example, with `796` Tit For Tat wins:: +example, with `1408` Tit For Tat wins:: >>> tournament = axl.Tournament( ... players=first_tournament_participants_ordered_by_reported_rank, ... turns=200, ... repetitions=5, - ... seed=796, + ... seed=1408, ... ) >>> results = tournament.play() >>> for name in results.ranked_names: @@ -169,19 +172,19 @@ example, with `796` Tit For Tat wins:: First by Graaskamp: 0.05 First by Downing First by Feld: 1.0, 0.5, 200 - First by Joss: 0.9 First by Tullock + First by Joss: 0.9 First by Anonymous Random: 0.5 -With `208` the strategy submitted by Grofman wins:: +With `136` the strategy submitted by Grofman wins:: >>> tournament = axl.Tournament( ... players=first_tournament_participants_ordered_by_reported_rank, ... turns=200, ... repetitions=5, - ... seed=208 + ... seed=136 ... ) >>> results = tournament.play() >>> for name in results.ranked_names: @@ -192,14 +195,13 @@ With `208` the strategy submitted by Grofman wins:: First by Shubik First by Tideman and Chieruzzi: (D, D) First by Nydegger - First by Davis: 10 Grudger - First by Graaskamp: 0.05 + First by Davis: 10 First by Downing + First by Graaskamp: 0.05 First by Feld: 1.0, 0.5, 200 - First by Tullock First by Joss: 0.9 - First by Anonymous + First by Tullock Random: 0.5 - + First by Anonymous From 79c30974fdf30cd546cb039c84265b74dee228d0 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Wed, 29 Jul 2020 21:29:09 -0700 Subject: [PATCH 080/121] Rework memory one players --- axelrod/strategies/memoryone.py | 74 ++++++++++--------- axelrod/tests/strategies/test_memoryone.py | 86 ++++++++++++++++++++-- 2 files changed, 122 insertions(+), 38 deletions(-) diff --git a/axelrod/strategies/memoryone.py b/axelrod/strategies/memoryone.py index 9dcf29aaf..a46dc9fcf 100644 --- a/axelrod/strategies/memoryone.py +++ b/axelrod/strategies/memoryone.py @@ -10,6 +10,38 @@ C, D = Action.C, Action.D +class WinStayLoseShift(Player): + """ + Win-Stay Lose-Shift, also called Pavlov. + + Names: + + - Win Stay Lose Shift: [Nowak1993]_ + - WSLS: [Stewart2012]_ + - Pavlov: [Kraines1989]_ + """ + + name = "Win-Stay Lose-Shift" + classifier = { + "memory_depth": 1, # Memory-one Four-Vector + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + def strategy(self, opponent: Player) -> Action: + if not self.history: + return C + # React to the opponent's last move + last_round = (self.history[-1], opponent.history[-1]) + if last_round == (C, C) or last_round == (D, D): + return C + return D + + class MemoryOnePlayer(Player): """ Uses a four-vector for strategies based on the last round of play, @@ -40,8 +72,7 @@ def __init__( """ Parameters ---------- - - fourvector: list or tuple of floats of length 4 + four_vector: list or tuple of floats of length 4 The response probabilities to the preceding round of play ( P(C|CC), P(C|CD), P(C|DC), P(C|DD) ) initial: C or D @@ -52,7 +83,7 @@ def __init__( Alternator is equivalent to MemoryOnePlayer((0, 0, 1, 1), C) Cooperator is equivalent to MemoryOnePlayer((1, 1, 1, 1), C) - Defector is equivalent to MemoryOnePlayer((0, 0, 0, 0), C) + Defector is equivalent to MemoryOnePlayer((0, 0, 0, 0), D) Random is equivalent to MemoryOnePlayer((0.5, 0.5, 0.5, 0.5)) (with a random choice for the initial state) TitForTat is equivalent to MemoryOnePlayer((1, 0, 1, 0), C) @@ -79,9 +110,14 @@ def set_four_vector(self, four_vector: Tuple[float, float, float, float]): "An element in the probability vector, {}, is not " "between 0 and 1.".format(str(four_vector)) ) - self._four_vector = dict(zip([(C, C), (C, D), (D, C), (D, D)], four_vector)) - self.classifier["stochastic"] = any(0 < x < 1 for x in set(four_vector)) + + def _post_init(self): + # Adjust classifiers + values = set(self._four_vector.values()) + self.classifier["stochastic"] = any(0 < x < 1 for x in values) + if all(x == 0 for x in values) or all(x == 1 for x in values): + self.classifier["memory_depth"] = 0 def strategy(self, opponent: Player) -> Action: if len(opponent.history) == 0: @@ -95,34 +131,6 @@ def strategy(self, opponent: Player) -> Action: return D if p == 0 else C -class WinStayLoseShift(MemoryOnePlayer): - """ - Win-Stay Lose-Shift, also called Pavlov. - - Names: - - - Win Stay Lose Shift: [Nowak1993]_ - - WSLS: [Stewart2012]_ - - Pavlov: [Kraines1989]_ - """ - - name = "Win-Stay Lose-Shift" - classifier = { - "memory_depth": 1, # Memory-one Four-Vector - "stochastic": False, - "makes_use_of": set(), - "long_run_time": False, - "inspects_source": False, - "manipulates_source": False, - "manipulates_state": False, - } - - def __init__(self, initial: Action = C) -> None: - four_vector = (1, 0, 0, 1) - super().__init__(four_vector) - self._initial = initial - - class WinShiftLoseStay(MemoryOnePlayer): """Win-Shift Lose-Stay, also called Reverse Pavlov. diff --git a/axelrod/tests/strategies/test_memoryone.py b/axelrod/tests/strategies/test_memoryone.py index 93c9abc04..d1ca82403 100644 --- a/axelrod/tests/strategies/test_memoryone.py +++ b/axelrod/tests/strategies/test_memoryone.py @@ -6,13 +6,17 @@ from axelrod.strategies.memoryone import MemoryOnePlayer from .test_player import TestPlayer, test_four_vector +from .test_alternator import TestAlternator +from .test_cooperator import TestCooperator +from .test_defector import TestDefector +from .test_titfortat import TestTitForTat C, D = axl.Action.C, axl.Action.D class TestWinStayLoseShift(TestPlayer): - name = "Win-Stay Lose-Shift: C" + name = "Win-Stay Lose-Shift" player = axl.WinStayLoseShift expected_classifier = { "memory_depth": 1, @@ -24,11 +28,8 @@ class TestWinStayLoseShift(TestPlayer): "manipulates_state": False, } - def test_class_classification(self): - self.assertEqual(self.player.classifier, self.expected_classifier) - def test_strategy(self): - # Check that switches if does not get best payoff. + # Check that player switches if does not get best payoff. actions = [(C, C), (C, D), (D, C), (D, D), (C, C)] self.versus_test(opponent=axl.Alternator(), expected_actions=actions) @@ -299,3 +300,78 @@ def test_subclass(self): self.assertIsInstance(self.p1, MemoryOnePlayer) self.assertIsInstance(self.p2, MemoryOnePlayer) self.assertIsInstance(self.p3, MemoryOnePlayer) + + +class TestMemoryOneAlternator(TestAlternator): + """Alternator is equivalent to MemoryOnePlayer((0, 0, 1, 1), C)""" + name = "Generic Memory One Player: (0, 0, 1, 1), C" + player = lambda x: axl.MemoryOnePlayer(four_vector=(0, 0, 1, 1)) + expected_classifier = { + "memory_depth": 1, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +class TestMemoryOneCooperator(TestCooperator): + """Cooperator is equivalent to MemoryOnePlayer((1, 1, 1, 1), C)""" + name = "Generic Memory One Player: (1, 1, 1, 1), C" + player = lambda x: axl.MemoryOnePlayer(four_vector=(1, 1, 1, 1)) + expected_classifier = { + "memory_depth": 0, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +class TestMemoryOneDefector(TestDefector): + """Defector is equivalent to MemoryOnePlayer((0, 0, 0, 0), D)""" + name = "Generic Memory One Player: (0, 0, 0, 0), D" + player = lambda x: axl.MemoryOnePlayer(four_vector=(0, 0, 0, 0), initial=D) + expected_classifier = { + "memory_depth": 0, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +class TestMemoryOneTitForTat(TestTitForTat): + """TitForTat is equivalent to MemoryOnePlayer((1, 0, 1, 0), C)""" + name = "Generic Memory One Player: (1, 0, 1, 0), C" + player = lambda x: axl.MemoryOnePlayer(four_vector=(1, 0, 1, 0)) + expected_classifier = { + "memory_depth": 1, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +class TestMemoryOneWSLS(TestWinStayLoseShift): + """WinStayLoseShift is equivalent to MemoryOnePlayer((1, 0, 0, 1), C)""" + name = "Generic Memory One Player: (1, 0, 0, 1), C" + player = lambda x: axl.MemoryOnePlayer(four_vector=(1, 0, 0, 1)) + expected_classifier = { + "memory_depth": 1, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } From e42041ec6acfb2cb45fc1628d835c328f2a4ff8a Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Wed, 29 Jul 2020 21:29:25 -0700 Subject: [PATCH 081/121] Rework memory two players --- axelrod/strategies/memorytwo.py | 47 ++++++++++++++---- axelrod/tests/strategies/test_memorytwo.py | 58 +++++++++++++++++++++- 2 files changed, 95 insertions(+), 10 deletions(-) diff --git a/axelrod/strategies/memorytwo.py b/axelrod/strategies/memorytwo.py index 623f3a2ff..61d725d55 100644 --- a/axelrod/strategies/memorytwo.py +++ b/axelrod/strategies/memorytwo.py @@ -34,7 +34,7 @@ class MemoryTwoPlayer(Player): 13. P(C|DD, CC) 14. P(C|DD, CD) 15. P(C|DD, DC) - 16. P(C|DD, DD)) + 16. P(C|DD, DD) Cooperator is set as the default player if sixteen_vector is not given. Names @@ -54,20 +54,19 @@ class MemoryTwoPlayer(Player): } def __init__( - self, sixteen_vector: Tuple[float, ...] = None, initial: Optional[Action] = None + self, sixteen_vector: Tuple[float, ...] = None, initial: Optional[Tuple[Action, Action]] = None ) -> None: """ Parameters ---------- - sixteen_vector: list or tuple of floats of length 16 The response probabilities to the preceding round of play - initial: C or D + initial: (Action, Action) The initial 2 moves """ super().__init__() if initial is None: - initial = C + initial = (C, C) self._initial = initial self.set_initial_sixteen_vector(sixteen_vector) @@ -92,11 +91,41 @@ def set_sixteen_vector(self, sixteen_vector: Tuple): self._sixteen_vector = dict( zip(states, sixteen_vector) ) # type: Dict[tuple, float] - self.classifier["stochastic"] = any(0 < x < 1 for x in set(sixteen_vector)) + + @staticmethod + def compute_memory_depth(sixteen_vector): + values = set(list(sixteen_vector.values())) + + # Memory-depth 0 + if all(x == 0 for x in values) or all(x == 1 for x in values): + return 0 + + is_memory_one = True + d = sixteen_vector + contexts = [(C, C), (C, D), (D, C), (D, D)] + + for c1 in contexts: + values = set() + i, j = c1 + for c2 in contexts: + x, y = c2 + values.add(d[((x, i), (y, j))]) + if len(values) > 1: + is_memory_one = False + break + if is_memory_one: + return 1 + return 2 + + def _post_init(self): + values = set(self._sixteen_vector.values()) + self.classifier["stochastic"] = any(0 < x < 1 for x in values) + self.classifier["memory_depth"] = self.compute_memory_depth(self._sixteen_vector) def strategy(self, opponent: Player) -> Action: - if len(opponent.history) <= 1: - return self._initial + turn = len(self.history) + if turn <= 1: + return self._initial[turn] # Determine which probability to use p = self._sixteen_vector[ (tuple(self.history[-2:]), tuple(opponent.history[-2:])) @@ -105,7 +134,7 @@ def strategy(self, opponent: Player) -> Action: try: return self._random.random_choice(p) except AttributeError: - return D if p == 0 else C + return C if p == 1 else D class AON2(MemoryTwoPlayer): diff --git a/axelrod/tests/strategies/test_memorytwo.py b/axelrod/tests/strategies/test_memorytwo.py index 285999022..2f6a6529c 100644 --- a/axelrod/tests/strategies/test_memorytwo.py +++ b/axelrod/tests/strategies/test_memorytwo.py @@ -7,6 +7,9 @@ from axelrod.strategies.memorytwo import MemoryTwoPlayer from .test_player import TestPlayer +from .test_alternator import TestAlternator +from .test_cooperator import TestCooperator +from .test_defector import TestDefector C, D = axl.Action.C, axl.Action.D @@ -82,7 +85,7 @@ class TestMemoryStochastic(TestPlayer): ) player = axl.MemoryTwoPlayer expected_classifier = { - "memory_depth": 2, # Memory-two Sixteen-Vector + "memory_depth": 0, # Memory-two Sixteen-Vector "stochastic": False, "makes_use_of": set(), "long_run_time": False, @@ -249,3 +252,56 @@ def test_strategy(self): expected_actions=actions, attrs={"play_as": "ALLD", "shift_counter": -1, "alld_counter": 2}, ) + + +class TestMemoryTwoCooperator(TestCooperator): + """Cooperator is equivalent to MemoryOnePlayer((1, 1, 1, 1), C)""" + name = "Generic Memory Two Player: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], (C, C)" + player = lambda x: axl.MemoryTwoPlayer(sixteen_vector=[1] * 16, initial=(C, C)) + expected_classifier = { + "memory_depth": 0, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +class TestMemoryTwoDefector(TestDefector): + """Defector is equivalent to MemoryOnePlayer((0, 0, 0, 0), D)""" + name = "Generic Memory Two Player: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], (D, D)" + player = lambda x: axl.MemoryTwoPlayer(sixteen_vector=[0] * 16, initial=(D, D)) + expected_classifier = { + "memory_depth": 0, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +def four_vector_to_sixteen_vector(four_vector): + a, b, c, d = four_vector + sixteen_vector = [a, b, a, b, d, c, d, c] * 2 + return sixteen_vector + + +class TestMemoryTwoAlternator(TestAlternator): + """Alternator is equivalent to MemoryOnePlayer((0, 0, 1, 1), C)""" + name = "Generic Memory Two Player: [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1], (C, D)" + player = lambda x: axl.MemoryTwoPlayer(sixteen_vector=four_vector_to_sixteen_vector((0, 0, 1, 1)), initial=(C, D)) + # player = lambda x: axl.MemoryTwoPlayer(sixteen_vector=four_vector_to_sixteen_vector((1, 1, 0, 0)), initial=(C, D)) + expected_classifier = { + "memory_depth": 1, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + From 18106176cc3583242c3f6cc5c10f30c2d6e51b2b Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Wed, 29 Jul 2020 21:30:37 -0700 Subject: [PATCH 082/121] Add EvolvablePlayer seed propagationtests --- axelrod/evolvable_player.py | 13 ++++------- .../tests/strategies/test_evolvable_player.py | 23 ++++++++++++++++++- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/axelrod/evolvable_player.py b/axelrod/evolvable_player.py index 0fbbad9c4..57926bac6 100644 --- a/axelrod/evolvable_player.py +++ b/axelrod/evolvable_player.py @@ -11,12 +11,6 @@ def __init__(self, *args): super().__init__(*args) -class SeedNotGivenError(Exception): - """Error indicating that a required seed was not supplied.""" - def __init__(self, *args): - super().__init__(*args) - - class EvolvablePlayer(Player): """A class for a player that can evolve, for use in the Moran process or with reinforcement learning algorithms. @@ -27,8 +21,9 @@ class EvolvablePlayer(Player): parent_class = Player parent_kwargs = [] # type: List[str] - # Parameter seed is actually required. 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) @@ -38,7 +33,9 @@ def overwrite_init_kwargs(self, **kwargs): 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 in my the mutation and crossover methods.""" init_kwargs = self.init_kwargs.copy() init_kwargs.update(kwargs) # Propagate seed forward for reproducibility. diff --git a/axelrod/tests/strategies/test_evolvable_player.py b/axelrod/tests/strategies/test_evolvable_player.py index 40a39cc8a..97cba8e9b 100644 --- a/axelrod/tests/strategies/test_evolvable_player.py +++ b/axelrod/tests/strategies/test_evolvable_player.py @@ -54,6 +54,8 @@ def crossover(self, other): class TestEvolvablePlayer(TestPlayer): + """Additional tests for EvolvablePlayers, in addition to the many tests + inherited from TestPlayer.""" player_class = EvolvableTestOpponent parent_class = None @@ -89,7 +91,7 @@ def test_randomization(self): player2 = self.player(seed=1) self.assertEqual(player1, player2) - for seed_ in range(2, 20): + for seed_ in range(2, 200): player2 = self.player(seed=seed_) if player1 != player2: return @@ -184,6 +186,25 @@ def test_behavior(self): deserialized_player = player.__class__.deserialize_parameters(serialized) self.behavior_test(deserialized_player, parent_player) + def test_seed_propagation(self): + """Tests that _create_new should typically alter the random seed.""" + player = self.player(seed=1) + for _ in range(100): + player = player.create_new() + if player._seed != 1: + return + + # Should never get here unless a change breaks the test, so don't include in coverage. + self.assertFalse(True) # pragma: no cover + + def test_seed_preservation(self): + """Tests that method function clone preserves the random seed. The seed + is intentionally not checked by Player.__eq__ so an explicit extra test + is needed.""" + player = self.player(seed=1) + clone = player.clone() + self.assertEqual(player._seed, clone._seed) + class TestUtilityFunctions(unittest.TestCase): From 1c2a8a812f678d98e986a49f107fd1b86031c429 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Thu, 30 Jul 2020 18:39:32 -0700 Subject: [PATCH 083/121] Update fingerprints and strategy_transformers to work properly with Meta classes --- axelrod/fingerprint.py | 18 +++--- axelrod/strategy_transformers.py | 9 ++- axelrod/tests/unit/test_fingerprint.py | 34 ++++++++++- .../tests/unit/test_strategy_transformers.py | 61 +++++++++++++++++++ 4 files changed, 111 insertions(+), 11 deletions(-) diff --git a/axelrod/fingerprint.py b/axelrod/fingerprint.py index 9baad139c..264856a15 100644 --- a/axelrod/fingerprint.py +++ b/axelrod/fingerprint.py @@ -42,7 +42,7 @@ def _create_points(step: float, progress_bar: bool = True) -> List[Point]: num = int((1 / step) // 1) + 1 if progress_bar: - p_bar = tqdm.tqdm(total=num ** 2, desc="Generating points") + p_bar = tqdm.tqdm(total=num**2, desc="Generating points") points = [] for x in np.linspace(0, 1, num): @@ -80,17 +80,18 @@ def _create_jossann(point: Point, probe: Any) -> Player: x, y = point if isinstance(probe, axl.Player): + probe_class = probe.__class__ init_kwargs = probe.init_kwargs - probe = probe.__class__ else: + probe_class = probe init_kwargs = {} if x + y >= 1: - joss_ann = DualTransformer()(JossAnnTransformer((1 - x, 1 - y))(probe))( - **init_kwargs - ) + joss_ann = DualTransformer()( + JossAnnTransformer((1 - x, 1 - y))( + probe_class))(**init_kwargs) else: - joss_ann = JossAnnTransformer((x, y))(probe)(**init_kwargs) + joss_ann = JossAnnTransformer((x, y))(probe_class)(**init_kwargs) return joss_ann @@ -128,7 +129,7 @@ def _create_edges(points: List[Point], progress_bar: bool = True) -> list: """Creates a set of edges for a spatial tournament. Constructs edges that correspond to `points`. All edges begin at 0, and - connect to the index +1 of the probe. + connect to the index + 1 of the probe. Parameters ---------- @@ -137,7 +138,6 @@ def _create_edges(points: List[Point], progress_bar: bool = True) -> list: progress_bar : bool Whether or not to create a progress bar which will be updated - Returns ---------- edges : list of tuples @@ -263,7 +263,7 @@ def _construct_tournament_elements( ) if isinstance(self.strategy, axl.Player): - tournament_players = [self.strategy] + probe_players + tournament_players = [self.strategy.clone()] + probe_players else: tournament_players = [self.strategy()] + probe_players diff --git a/axelrod/strategy_transformers.py b/axelrod/strategy_transformers.py index f0397fdba..435591eb6 100644 --- a/axelrod/strategy_transformers.py +++ b/axelrod/strategy_transformers.py @@ -305,7 +305,6 @@ def generic_strategy_wrapper(player, opponent, proposed_action, *args, **kwargs) Returns ------- action: an axelrod.Action, C or D - """ # This example just passes through the proposed_action @@ -638,6 +637,12 @@ def joss_ann_wrapper(player, opponent, proposed_action, probability): action = player._random.choice(options, p=probability) return action + # try: + # action = player._random.choice(options, p=probability) + # except AttributeError: + # print(orig_prob, probability, player.name, player.classifier) + # return action + def jossann_reclassifier(original_classifier, probability): """ @@ -648,6 +653,8 @@ def jossann_reclassifier(original_classifier, probability): probability = tuple([i / sum(probability) for i in probability]) if probability in [(1, 0), (0, 1)]: + # In this case the player's strategy is never actually called, + # so even if it were stochastic the play is not. original_classifier["stochastic"] = False else: original_classifier["stochastic"] = True diff --git a/axelrod/tests/unit/test_fingerprint.py b/axelrod/tests/unit/test_fingerprint.py index 7a5ae1629..c9ad25b19 100644 --- a/axelrod/tests/unit/test_fingerprint.py +++ b/axelrod/tests/unit/test_fingerprint.py @@ -381,11 +381,43 @@ def test_pair_fingerprints(self, strategy_pair): A test to check that we can fingerprint with any two given strategies or instances """ + # strategy, probe = strategy_pair + # af = AshlockFingerprint(strategy, probe) + # data = af.fingerprint(turns=2, repetitions=2, step=0.5, progress_bar=False, seed=1) + # self.assertIsInstance(data, dict) + # + # strategy, probe = strategy_pair + # af = AshlockFingerprint(strategy(), probe) + # data = af.fingerprint(turns=2, repetitions=2, step=0.5, progress_bar=False, seed=2) + # self.assertIsInstance(data, dict) + # + # strategy, probe = strategy_pair + # af = AshlockFingerprint(strategy, probe()) + # data = af.fingerprint(turns=2, repetitions=2, step=0.5, progress_bar=False, seed=3) + # self.assertIsInstance(data, dict) + strategy, probe = strategy_pair af = AshlockFingerprint(strategy(), probe()) - data = af.fingerprint(turns=2, repetitions=2, step=0.5, progress_bar=False) + data = af.fingerprint(turns=2, repetitions=2, step=0.5, progress_bar=False, seed=4) self.assertIsInstance(data, dict) + @given(strategy_pair=strategy_lists(min_size=2, max_size=2)) + @settings(max_examples=5, deadline=None) + def test_fingerprint_reproducibility(self, strategy_pair): + """ + A test to check that we can fingerprint + with any two given strategies or instances + """ + strategy, probe = strategy_pair + af = AshlockFingerprint(strategy(), probe()) + data = af.fingerprint(turns=2, repetitions=2, step=0.5, progress_bar=False, seed=4) + + strategy, probe = strategy_pair + af = AshlockFingerprint(strategy(), probe()) + data2 = af.fingerprint(turns=2, repetitions=2, step=0.5, progress_bar=False, seed=4) + + self.assertEqual(data, data2) + class TestTransitiveFingerprint(unittest.TestCase): def test_init(self): diff --git a/axelrod/tests/unit/test_strategy_transformers.py b/axelrod/tests/unit/test_strategy_transformers.py index fdae5483f..6a8bd51bc 100644 --- a/axelrod/tests/unit/test_strategy_transformers.py +++ b/axelrod/tests/unit/test_strategy_transformers.py @@ -196,6 +196,53 @@ def test_compose_transformers(self): p2 = axl.Cooperator() self.versus_test(p1, p2, [D, D, C, C, C, C, D, D], [C] * 8) + def test_reclassification(self): + """Tests that reclassifiers properly work.""" + + def stochastic_reclassifier(original_classifier, *args): + original_classifier["stochastic"] = True + return original_classifier + + def deterministic_reclassifier(original_classifier, *args): + original_classifier["stochastic"] = False + return original_classifier + + StochasticTransformer = StrategyTransformerFactory( + generic_strategy_wrapper, reclassifier=stochastic_reclassifier) + DeterministicTransformer = StrategyTransformerFactory( + generic_strategy_wrapper, reclassifier=deterministic_reclassifier) + + # Cooperator is not stochastic + self.assertFalse(axl.Cooperator().classifier["stochastic"]) + # Transform makes it stochastic + player = StochasticTransformer()(axl.Cooperator)() + self.assertTrue(player.classifier["stochastic"]) + + # Composing transforms should return it to not being stochastic + cls1 = compose_transformers(DeterministicTransformer(), StochasticTransformer()) + player = cls1(axl.Cooperator)() + self.assertFalse(player.classifier["stochastic"]) + + # Explicit composition + player = DeterministicTransformer()(StochasticTransformer()(axl.Cooperator))() + self.assertFalse(player.classifier["stochastic"]) + + # Random is stochastic + self.assertTrue(axl.Random().classifier["stochastic"]) + + # Transformer makes is not stochastic + player = DeterministicTransformer()(axl.Random)() + self.assertFalse(player.classifier["stochastic"]) + + # Composing transforms should return it to being stochastic + cls1 = compose_transformers(StochasticTransformer(), DeterministicTransformer()) + player = cls1(axl.Random)() + self.assertTrue(player.classifier["stochastic"]) + + # Explicit composition + player = StochasticTransformer()(DeterministicTransformer()(axl.Random))() + self.assertTrue(player.classifier["stochastic"]) + def test_nilpotency(self): """Show that some of the transformers are (sometimes) nilpotent, i.e. that transformer(transformer(PlayerClass)) == PlayerClass""" @@ -314,6 +361,9 @@ def test_dual_jossann_regression_test(self): player_class = JossAnnTransformer((0.5, 0.4))(axl.EvolvedLookerUp2_2_2) self.assert_dual_wrapper_correct(player_class) + player_class = JossAnnTransformer((0.2, 0.8))(axl.MetaHunter) + self.assert_dual_wrapper_correct(player_class) + def test_dual_transformer_simple_play_regression_test(self): """DualTransformer has failed when there were multiple DualTransformers. It has also failed when DualTransformer was not the outermost @@ -500,6 +550,17 @@ def test_deterministic_match(self): self.assertFalse(axl.Classifiers["stochastic"](p1)) self.versus_test(p1, p2, [D] * 5, [C] * 5) + def test_meta_strategy(self): + """Tests the JossAnn transformer on a Meta strategy to check + for a regression.""" + probability = (1, 0) + for player_class in [axl.MetaHunter, axl.MemoryDecay, axl.MetaWinner]: + p1 = JossAnnTransformer(probability)(player_class)() + self.assertFalse(axl.Classifiers["stochastic"](p1)) + p2 = axl.Cooperator() + m = axl.Match((p1, p2), turns=10) + m.play() + def test_deterministic_match_override(self): """Tests the JossAnn transformer.""" probability = (1, 0) From d0859ab0d5b804640c8db6d5100aad131b3f9b7c Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Thu, 30 Jul 2020 18:40:24 -0700 Subject: [PATCH 084/121] Update Meta strategies to properly handle Joss-Ann edge cases --- axelrod/strategies/meta.py | 49 +++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/axelrod/strategies/meta.py b/axelrod/strategies/meta.py index 1869c52dc..a54c33813 100644 --- a/axelrod/strategies/meta.py +++ b/axelrod/strategies/meta.py @@ -49,16 +49,16 @@ def __init__(self, team=None): if team: self.team = team else: - # Needs to be computed manually to prevent circular dependency self.team = ordinary_strategies # Make sure we don't use any meta players to avoid infinite recursion. self.team = [t for t in self.team if not issubclass(t, MetaPlayer)] # Initiate all the players in our team. self.team = [t() for t in self.team] - + self._last_results = None super().__init__() - # This player inherits the classifiers of its team. + def _post_init(self): + # The player's classification characteristics are derived from the team. # Note that memory_depth is not simply the max memory_depth of the team. for key in [ "stochastic", @@ -71,8 +71,6 @@ def __init__(self, team=None): for t in self.team: self.classifier["makes_use_of"].update(Classifiers["makes_use_of"](t)) - self._last_results = None - def set_seed(self, seed=None): super().set_seed(seed=seed) # Seed the team as well @@ -91,8 +89,22 @@ def __repr__(self): def update_histories(self, coplay): # Update team histories. - for player, play in zip(self.team, self._last_results): - player.history.append(play, coplay) + try: + for player, play in zip(self.team, self._last_results): + player.update_history(play, coplay) + except TypeError: + # If the Meta class is decorated by the Joss-Ann transformer, + # such that the decorated class is now deterministic, the underlying + # strategy isn't called. In that case, updating the history of all the + # team members doesn't matter. + # As a sanity check, look for at least one reclassifier, otherwise + # this try-except clause could hide a bug. + if len(self._reclassifiers) == 0: + raise TypeError("MetaClass update_histories issue, expected a transformer.") + # Otherwise just update with C always, so at least the histories have the + # expected length. + for player in self.team: + player.update_history(C, coplay) def update_history(self, play, coplay): super().update_history(play, coplay) @@ -209,7 +221,11 @@ class MetaWinnerEnsemble(MetaWinner): def __init__(self, team=None): super().__init__(team=team) - if len(self.team) > 1: + + def _post_init(self): + super()._post_init() + team = list(t.__class__ for t in self.team) + if len(team) > 1: self.classifier["stochastic"] = True self.singular = False else: @@ -217,7 +233,7 @@ def __init__(self, team=None): # If the team has repeated identical members, then it reduces to a singular team # and it may not actually be stochastic. if team and len(set(team)) == 1: - self.classifier["stochastic"] = Classifiers["stochastic"](team[0]()) + self.classifier["stochastic"] = Classifiers["stochastic"](self.team[0]) self.singular = True def meta_strategy(self, results, opponent): @@ -515,6 +531,10 @@ class MetaMixer(MetaPlayer): def __init__(self, team=None, distribution=None): super().__init__(team=team) + self.distribution = distribution + + def _post_init(self): + distribution = self.distribution if distribution and len(set(distribution)) > 1: self.classifier["stochastic"] = True if len(self.team) == 1: @@ -525,7 +545,6 @@ def __init__(self, team=None, distribution=None): return # Check if the distribution has only one non-zero value. If so, the strategy may be # deterministic, and we can avoid _random. - self.distribution = distribution if distribution: total = sum(distribution) if total == 0: @@ -678,10 +697,6 @@ def __init__( start_strategy_duration: int = 15, ): super().__init__(team=[start_strategy]) - # This strategy is stochastic even if none of the team is. The - # MetaPlayer initializer will set stochastic to be False in that case. - self.classifier["stochastic"] = True - self.p_memory_delete = p_memory_delete self.p_memory_alter = p_memory_alter self.loss_value = loss_value @@ -690,6 +705,12 @@ def __init__( self.start_strategy_duration = start_strategy_duration self.gloss_values = None + def _post_init(self): + super()._post_init() + # This strategy is stochastic even if none of the team is. The + # MetaPlayer initializer will set stochastic to be False in that case. + self.classifier["stochastic"] = True + def __repr__(self): return Player.__repr__(self) From 47b1871797ced68641b257aa24c349e2dc0368c4 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Thu, 30 Jul 2020 18:41:16 -0700 Subject: [PATCH 085/121] Update various docstrings and other cleanup --- axelrod/match.py | 7 +++ axelrod/player.py | 53 +++++++++++++++++-- axelrod/random_.py | 16 +++--- .../strategies/test_finite_state_machines.py | 2 + 4 files changed, 68 insertions(+), 10 deletions(-) diff --git a/axelrod/match.py b/axelrod/match.py index a85cc5b4a..907e85f44 100644 --- a/axelrod/match.py +++ b/axelrod/match.py @@ -93,6 +93,13 @@ def __init__( self.reset = reset def set_seed(self, seed): + """Sets a random seed for the Match, for reproducibility. Initializes + a match-wide RNG instance which is used to propagate seeds to the Players + and to generate random values for noise. Seeds are only set for stochastic players. + Any seeds set on Players before being passed to Match will be overwritten. + However, Evolvable Players may have already used their seeds to initialize + their parameters, if underspecified. + """ self.seed = seed self._random = RandomGenerator(seed=self.seed) diff --git a/axelrod/player.py b/axelrod/player.py index de73e8c53..1316c018d 100644 --- a/axelrod/player.py +++ b/axelrod/player.py @@ -16,9 +16,57 @@ class PostInitCaller(type): - """Metaclass to be able to handle post __init__ tasks.""" + """Metaclass to be able to handle post __init__ tasks. + If there is a DerivedPlayer class of Player that overrides + _post_init, as follows: + + class Player(object, metaclass=PostInitCaller): + def __new__(cls, *args, **kwargs): + print("Player.__new__") + obj = super().__new__(cls) + return obj + + def __init__(self): + print("Player.__init__") + + def _post_init(self): + print("Player._post_init") + + def _post_transform(self): + print("Player._post_transform") + + + class DerivedPlayer(Player): + def __init__(self): + print("DerivedPlayer.__init__") + super().__init__() + + def _post_init(self): + print("DerivedPlayer._post_init") + super()._post_init() + + + dp = DerivedPlayer() + + Then the call order is: + * PostInitCaller.__call__ + * Player.__new__ + * DerivedPlayer.__init__ + * Player.__init__ + * DerivedPlayer._post_init + * Player._post_init + * Player._post_transform + + See here to learn more: https://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/ + """ def __call__(cls, *args, **kwargs): + # This calls cls.__new__ and cls.__init__ obj = type.__call__(cls, *args, **kwargs) + # Next we do any post init or post transform tasks, like recomputing + # classifiers + # Note that subclasses inherit the metaclass, and subclasses my override + # or extend __init__ so it's necessary to do these tasks after all the + # __init__'s have run in the case of a post-transform reclassification. obj._post_init() obj._post_transform() return obj @@ -75,11 +123,10 @@ def _post_transform(self): # Reclassify strategy post __init__, if needed. for (reclassifier, args, kwargs) in self._reclassifiers: self.classifier = reclassifier(self.classifier, *args, **kwargs) - pass def __eq__(self, other): """ - Test if two players are equal. + Test if two players are equal, ignoring random seed and RNG state. """ if self.__repr__() != other.__repr__(): return False diff --git a/axelrod/random_.py b/axelrod/random_.py index 39544007b..af52c9f00 100644 --- a/axelrod/random_.py +++ b/axelrod/random_.py @@ -114,8 +114,9 @@ def sample(self): class BulkRandomGenerator(object): - """Bulk generator of random integers for tournament seeding - and reproducibility. Use like a generator.""" + """Bulk generator of random integers for tournament seeding and + reproducibility. Bulk generation of random values is more efficient. + Use this class like a generator.""" def __init__(self, seed=None, batch_size=1000): self._random_generator = RandomState() self._random_generator.seed(seed) @@ -125,18 +126,19 @@ def __init__(self, seed=None, batch_size=1000): self._fill_ints() def _fill_ints(self): - ints = self._random_generator.randint( + # Generate more random values. Store as a list since generators + # cannot be pickled. + self._ints = self._random_generator.randint( low=0, high=2**32 - 1, size=self._batch_size) - self._ints = [x for x in ints] self._index = 0 def __next__(self): try: x = self._ints[self._index] - self._index += 1 - return x except IndexError: self._fill_ints() - return self.__next__() + x = self._ints[self._index] + self._index += 1 + return x diff --git a/axelrod/tests/strategies/test_finite_state_machines.py b/axelrod/tests/strategies/test_finite_state_machines.py index 2ad48ed8b..55f21cd05 100644 --- a/axelrod/tests/strategies/test_finite_state_machines.py +++ b/axelrod/tests/strategies/test_finite_state_machines.py @@ -1135,9 +1135,11 @@ class EvolvableFSMAsFSM(TestFSMPlayer): player = EvolvableFSMPlayerWithDefault def test_equality_of_clone(self): + # Can't pickle a Partialed Class pass def test_equality_of_pickle_clone(self): + # Can't pickle a Partialed Class pass def test_repr(self): From c3a07869c58014f6d02ea68a78e9e8b227973750 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Thu, 30 Jul 2020 18:47:02 -0700 Subject: [PATCH 086/121] Update classification docs tests --- docs/tutorials/advanced/classification_of_strategies.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/advanced/classification_of_strategies.rst b/docs/tutorials/advanced/classification_of_strategies.rst index 984607f7e..89e1ec4b1 100644 --- a/docs/tutorials/advanced/classification_of_strategies.rst +++ b/docs/tutorials/advanced/classification_of_strategies.rst @@ -91,7 +91,7 @@ length of each match of the tournament:: ... } >>> strategies = axl.filtered_strategies(filterset) >>> len(strategies) - 23 + 22 Note that in the filterset dictionary, the value for the 'makes_use_of' key must be a list. Here is how we might identify the number of strategies that use @@ -102,7 +102,7 @@ both the length of the tournament and the game being played:: ... } >>> strategies = axl.filtered_strategies(filterset) >>> len(strategies) - 16 + 15 Some strategies have been classified as having a particularly long run time:: From 9b2efb38b7a7f35413dcc9cfc0cdda21dde921c0 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Thu, 30 Jul 2020 21:19:59 -0700 Subject: [PATCH 087/121] Update Joss Ann reclassifier + a test --- axelrod/strategy_transformers.py | 11 ++++++++++- axelrod/tests/unit/test_strategy_transformers.py | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/axelrod/strategy_transformers.py b/axelrod/strategy_transformers.py index 2861e1724..d7de2f9d4 100644 --- a/axelrod/strategy_transformers.py +++ b/axelrod/strategy_transformers.py @@ -683,7 +683,16 @@ def jossann_reclassifier(original_classifier, probability): if probability in [(1, 0), (0, 1)]: # In this case the player's strategy is never actually called, # so even if it were stochastic the play is not. - original_classifier["stochastic"] = False + # Also the other classifiers are nulled as well. + original_classifier = { + "memory_depth": 0, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } else: original_classifier["stochastic"] = True diff --git a/axelrod/tests/unit/test_strategy_transformers.py b/axelrod/tests/unit/test_strategy_transformers.py index 73dcdef8b..1a3db576b 100644 --- a/axelrod/tests/unit/test_strategy_transformers.py +++ b/axelrod/tests/unit/test_strategy_transformers.py @@ -993,3 +993,17 @@ class TestJossAnnDual(TestPlayer): "manipulates_source": False, "manipulates_state": False, } + + +class TestJossAnnOverwriteClassifier(TestPlayer): + name = "Joss-Ann Final Random: 0.5: [D, D]: (1.0, 0.0)" + player = JossAnnTransformer((1., 0.))(FinalTransformer([D, D])(axl.Random)) + expected_classifier = { + "memory_depth": 0, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } From 5128e4d8f6762986e5449564349d8874f7a54802 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Thu, 30 Jul 2020 21:26:58 -0700 Subject: [PATCH 088/121] Remove yaml testing code and data classes dependent on py3, used to find new random seeds --- axelrod/data_classes.py | 111 ----------------------- axelrod/tests/unit/test_data_classes.py | 13 --- axelrod/yaml.py | 114 ------------------------ 3 files changed, 238 deletions(-) delete mode 100644 axelrod/data_classes.py delete mode 100644 axelrod/tests/unit/test_data_classes.py delete mode 100644 axelrod/yaml.py diff --git a/axelrod/data_classes.py b/axelrod/data_classes.py deleted file mode 100644 index 2f8c1857c..000000000 --- a/axelrod/data_classes.py +++ /dev/null @@ -1,111 +0,0 @@ -from __future__ import annotations -from dataclasses import dataclass -from typing import List, Optional, Tuple - -import axelrod -from axelrod.action import Action - - -def verify_match_outcomes(match, expected_actions1, expected_actions2, attrs): - """Tests that match produces the expected results.""" - match.play() - player1, player2 = match.players - for (play, expected_play) in zip(player1.history, expected_actions1): - if play != expected_play: - # print(play, expected_play) - return False - for (play, expected_play) in zip(player2.history, expected_actions2): - # print(play, expected_play) - if play != expected_play: - return False - # Test final player attributes are as expected - if attrs: - for attr, value in attrs.items(): - if getattr(player1, attr) != value: - return False - return True - - -@dataclass -class PlayerConfig: - name: str - init_kwargs: dict = None - - def __call__(self): - # Look up player by name - player_class = getattr(axelrod, self.name) - if self.init_kwargs: - return player_class(**self.init_kwargs) - return player_class() - - -@dataclass -class MatchParameters: - turns: Optional[int] = None - noise: Optional[float] = None - prob_end: Optional[float] = None - seed: Optional[int] = None - game: Optional[axelrod.Game] = None - match_attributes: Optional[dict] = None - - -@dataclass -class ExpectedMatchOutcome: - player_actions: Tuple[Action, ...] - coplayer_actions: Tuple[Action, ...] - player_attributes: Optional[dict] = None - - -@dataclass -class MatchConfig: - player: PlayerConfig - coplayer: PlayerConfig - match_parameters: MatchParameters - expected_outcome: Optional[ExpectedMatchOutcome] = None - - def match(self, seed=None): - """Generate the match.""" - player = self.player() - coplayer = self.coplayer() - noise = self.match_parameters.noise - if not seed: - seed = self.match_parameters.seed - prob_end = self.match_parameters.prob_end - match_attributes = self.match_parameters.match_attributes - turns = len(self.expected_outcome.player_actions) - match = axelrod.Match( - (player, coplayer), - turns=turns, - noise=noise, - prob_end=prob_end, - seed=seed, - match_attributes=match_attributes - ) - return match - - def verify_match_outcomes(self, seed=None): - match = self.match(seed=seed) - verify_match_outcomes(match, - self.expected_outcome.player_actions, - self.expected_outcome.coplayer_actions, - self.expected_outcome.player_attributes) - - def search_seeds(self, lower=1, upper=100000): - """Searches for a working seed.""" - for seed in range(lower, upper): - match = self.match(seed=seed) - if verify_match_outcomes(match, - self.expected_outcome.player_actions, - self.expected_outcome.coplayer_actions, - self.expected_outcome.player_attributes): - return seed - return None - - -@dataclass -class TestMatchConfig: - name: str - description: Optional[str] - match_config: MatchConfig - - diff --git a/axelrod/tests/unit/test_data_classes.py b/axelrod/tests/unit/test_data_classes.py deleted file mode 100644 index 223800c20..000000000 --- a/axelrod/tests/unit/test_data_classes.py +++ /dev/null @@ -1,13 +0,0 @@ -import unittest -import axelrod as axl - - -class TestPlayerConfig(unittest.TestCase): - - def test_call(self): - pc = axl.PlayerConfig("Cooperator") - player = pc() - print(player) - self.assertTrue(issubclass(type(player), axl.Player)) - self.assertTrue(isinstance(player, axl.Cooperator)) - diff --git a/axelrod/yaml.py b/axelrod/yaml.py deleted file mode 100644 index 262fdd48f..000000000 --- a/axelrod/yaml.py +++ /dev/null @@ -1,114 +0,0 @@ -from dataclasses import asdict -import inspect -from typing import Tuple - -from dacite import from_dict, Config -import yaml - -import axelrod -from axelrod.action import Action, actions_to_str, str_to_actions -from axelrod.data_classes import ExpectedMatchOutcome, PlayerConfig, MatchConfig, MatchParameters, TestMatchConfig - -filename = "test_matches.yaml" -# f = open("test_matches.yaml", 'w') -# f.close() - - -def build_player_spec(name, init_kwargs=None): - # if name == "MockPlayer": - # init_kwargs["actions"] = actions_to_str(init_kwargs["actions"]) - return PlayerConfig(name=name, init_kwargs=dict(init_kwargs)) - - -def build_expected_spec(player_actions, coplayer_actions, player_attributes=None): - return ExpectedMatchOutcome( - player_actions=player_actions, - coplayer_actions=coplayer_actions, - player_attributes=player_attributes) - - -def build_match_parameters_spec(noise=None, seed=None, match_attributes=None): - return MatchParameters(noise=noise, seed=seed, match_attributes=match_attributes) - - -def build_match_spec(player_name, coplayer_name, player_actions, coplayer_actions, noise=None, seed=None, - player_init_kwargs=None, coplayer_init_kwargs=None, player_attributes=None, match_attributes=None): - return MatchConfig( - player=build_player_spec(player_name, init_kwargs=player_init_kwargs.copy()), - coplayer=build_player_spec(coplayer_name, init_kwargs=coplayer_init_kwargs.copy()), - match_parameters=build_match_parameters_spec(noise=noise, seed=seed, match_attributes=match_attributes), - expected_outcome=build_expected_spec(player_actions, coplayer_actions, player_attributes=player_attributes) - ) - - -def build_test_match_config(name, description, match_config): - return TestMatchConfig(name=name, description=description, match_config=match_config) - - -def log_kwargs(func): - def wrapper(*args, **kwargs): - try: - noise = kwargs["noise"] - except KeyError: - noise = None - try: - seed = kwargs["seed"] - except KeyError: - seed = None - try: - match_attributes = kwargs["match_attributes"] - except KeyError: - match_attributes = None - try: - player_attributes = kwargs["attrs"] - except KeyError: - player_attributes = None - - # Some inspect shenanigans to get the calling function name and docstring - outer_frame = inspect.getouterframes(inspect.currentframe(), context=2) - calling_frame_info = outer_frame[2] - function_name = calling_frame_info.function - code = calling_frame_info.frame.f_code - f = getattr(calling_frame_info.frame.f_locals['self'], code.co_name) - docstring = inspect.getdoc(f) - - test_config = build_test_match_config( - name=function_name, - description=docstring, - match_config=build_match_spec( - str(args[1].__class__.__name__), - str(args[2].__class__.__name__), - actions_to_str(args[-2]), actions_to_str(args[-1]), - noise=noise, seed=seed, - player_init_kwargs=args[1].init_kwargs, - coplayer_init_kwargs=args[2].init_kwargs, - match_attributes=match_attributes, - player_attributes=player_attributes) - ) - stream = open(filename, 'a') - try: - stream.write("---\n") - yaml.dump(asdict(test_config), stream) - except TypeError: - # Some players, like transformed players, don't serialize - # well - pass - stream.close() - return func(*args, **kwargs) - return wrapper - - -def load_matches(): - stream = open(filename, 'r') - matches = yaml.load_all(stream, Loader=yaml.Loader) - deserialized = [] - for match in matches: - try: - m = from_dict(data_class=TestMatchConfig, data=match, config=Config( - type_hooks={Tuple[Action, ...]: str_to_actions})) - deserialized.append(m) - except TypeError: - continue - return deserialized - - From e9bb9ce4cafe6b0ff348d59094e0752810719b5c Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Thu, 30 Jul 2020 21:28:44 -0700 Subject: [PATCH 089/121] Fix requirements.txt typo --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 03f169738..e3701489e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ cloudpickle>=0.2.1 fsspec>=0.4.3 dask>=2.3.0 -hypothesis=5.19 +hypothesis==5.19 matplotlib>=2.0.0 numpy>=1.9.2 pandas>=0.18.1 From 44bc97d9829acff384f32ac5f1982e2ea8e6cd2e Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Thu, 30 Jul 2020 21:39:03 -0700 Subject: [PATCH 090/121] Remove imports associated to yaml and dataclasses --- axelrod/__init__.py | 2 -- axelrod/strategy_transformers.py | 4 +++- axelrod/tests/strategies/test_player.py | 2 -- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/axelrod/__init__.py b/axelrod/__init__.py index f2abbc63e..573af1bb9 100644 --- a/axelrod/__init__.py +++ b/axelrod/__init__.py @@ -18,8 +18,6 @@ from axelrod.plot import Plot from axelrod.game import DefaultGame, Game from axelrod.history import History, LimitedHistory -from axelrod.data_classes import PlayerConfig -from axelrod.yaml import log_kwargs, load_matches from axelrod.player import Player from axelrod.classifier import Classifiers from axelrod.evolvable_player import EvolvablePlayer diff --git a/axelrod/strategy_transformers.py b/axelrod/strategy_transformers.py index d7de2f9d4..09c0a71c9 100644 --- a/axelrod/strategy_transformers.py +++ b/axelrod/strategy_transformers.py @@ -5,7 +5,6 @@ See the various Meta strategies for another type of transformation. """ -import inspect from importlib import import_module from typing import Any @@ -24,6 +23,8 @@ def makes_use_of_reclassifier(original_classifier, player_class, wrapper): + """Reclassifier for post-transformation determination of whether + strategy makes_use_of anything differently.""" classifier_makes_use_of = makes_use_of(player_class) classifier_makes_use_of.update(makes_use_of_variant(wrapper)) try: @@ -103,6 +104,7 @@ def __call__(self, PlayerClass): reclassifiers = PlayerClass._reclassifiers.copy() if reclassifier is not None: reclassifiers.append((makes_use_of_reclassifier, (PlayerClass, strategy_wrapper), {})) + # This one is second on the assumption that the wrapper reclassifier knows best. reclassifiers.append((reclassifier, args, kwargs)) # First handle the case where the strategy method is static. diff --git a/axelrod/tests/strategies/test_player.py b/axelrod/tests/strategies/test_player.py index f6caf3c4f..2c3dfec6f 100644 --- a/axelrod/tests/strategies/test_player.py +++ b/axelrod/tests/strategies/test_player.py @@ -2,12 +2,10 @@ import pickle import types import unittest -import warnings import axelrod as axl import numpy as np from axelrod.tests.property import strategy_lists -from axelrod.yaml import log_kwargs from hypothesis import given, settings from hypothesis.strategies import integers, sampled_from From 19b36f7272aad8c1892e3a59fa4e22997a063bf3 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Thu, 30 Jul 2020 22:01:19 -0700 Subject: [PATCH 091/121] Remove pinned hypothesis version from github workflow --- .github/workflows/config.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 4599f2d8f..553f4a0d2 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -9,7 +9,6 @@ on: jobs: build: - runs-on: ${{ matrix.os }} strategy: max-parallel: 4 @@ -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: | From 750f73cd4cb2f8c33413cd3fcb17a1f8117a9533 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Thu, 30 Jul 2020 22:49:27 -0700 Subject: [PATCH 092/121] Specify random seed type in attempt to make Windows happy --- axelrod/random_.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axelrod/random_.py b/axelrod/random_.py index af52c9f00..21df8939a 100644 --- a/axelrod/random_.py +++ b/axelrod/random_.py @@ -26,7 +26,7 @@ def randint(self, *args, **kwargs): return self._random.randint(*args, **kwargs) def random_seed_int(self): - return self.randint(0, 2**32-1) + return self.randint(0, 2**32-1, dtype='u8') def choice(self, *args, **kwargs): return self._random.choice(*args, **kwargs) From ea2599d4953a6fbf56779ac3022408b319e6f33d Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 31 Jul 2020 08:08:19 -0700 Subject: [PATCH 093/121] Add deadline=None to two tests to prevent errors on CI --- axelrod/tests/strategies/test_player.py | 2 +- axelrod/tests/unit/test_tournament.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/axelrod/tests/strategies/test_player.py b/axelrod/tests/strategies/test_player.py index 2c3dfec6f..fe37d466f 100644 --- a/axelrod/tests/strategies/test_player.py +++ b/axelrod/tests/strategies/test_player.py @@ -488,7 +488,7 @@ def test_clone_reproducible_play(self, seed, turns, noise): seed=integers(min_value=1, max_value=200), turns=integers(min_value=1, max_value=200), ) - @settings(max_examples=1) + @settings(max_examples=1, deadline=None) def test_memory_depth_upper_bound(self, strategies, seed, turns): """ Test that the memory depth is indeed an upper bound. diff --git a/axelrod/tests/unit/test_tournament.py b/axelrod/tests/unit/test_tournament.py index 8d5b7afbc..19f2ebc05 100644 --- a/axelrod/tests/unit/test_tournament.py +++ b/axelrod/tests/unit/test_tournament.py @@ -638,7 +638,7 @@ def test_no_build_result_set(self): self.assertEqual(len(calls), 0) @given(turns=integers(min_value=1, max_value=200)) - @settings(max_examples=5) + @settings(max_examples=5, deadline=None) @example(turns=3) @example(turns=axl.DEFAULT_TURNS) def test_play_matches(self, turns): From c72513816931444fe579118cf211fc4bcc2c6e04 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 31 Jul 2020 08:38:31 -0700 Subject: [PATCH 094/121] Attempt 2 to make windows happy with int64 random seed type --- axelrod/random_.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axelrod/random_.py b/axelrod/random_.py index 21df8939a..eef7a4d45 100644 --- a/axelrod/random_.py +++ b/axelrod/random_.py @@ -26,7 +26,7 @@ def randint(self, *args, **kwargs): return self._random.randint(*args, **kwargs) def random_seed_int(self): - return self.randint(0, 2**32-1, dtype='u8') + return self.randint(0, 2**32-1, dtype='int64') def choice(self, *args, **kwargs): return self._random.choice(*args, **kwargs) From e43d76a975d8914bd66aae93563b59a9aa9c117f Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 31 Jul 2020 09:03:13 -0700 Subject: [PATCH 095/121] Attempt 3 to make windows happy with int64 random seed type --- axelrod/random_.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axelrod/random_.py b/axelrod/random_.py index eef7a4d45..c2c3b3e4d 100644 --- a/axelrod/random_.py +++ b/axelrod/random_.py @@ -26,7 +26,7 @@ def randint(self, *args, **kwargs): return self._random.randint(*args, **kwargs) def random_seed_int(self): - return self.randint(0, 2**32-1, dtype='int64') + return self.randint(0, 2**32-1, dtype=np.int64) def choice(self, *args, **kwargs): return self._random.choice(*args, **kwargs) From 693de4f7e3db5c36bf75232a6cb64791d6e573e6 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 31 Jul 2020 09:42:26 -0700 Subject: [PATCH 096/121] Remove unused requirements and increase minimum versions --- docs/conf.py | 5 ----- requirements.txt | 21 ++++++++------------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 7d77121f6..72d0a774c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,10 +19,8 @@ import mock MOCK_MODULES = [ - "cloudpickle", "dask", "dask.dataframe", - "dill", "matplotlib", "matplotlib.pyplot", "matplotlib.transforms", @@ -40,9 +38,6 @@ "prompt_toolkit.validation", "scipy", "scipy.stats", - "toolz", - "toolz.curried", - "toolz.functoolz", "tqdm", "yaml", ] diff --git a/requirements.txt b/requirements.txt index e3701489e..5bb9ceeb8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,9 @@ -cloudpickle>=0.2.1 -fsspec>=0.4.3 -dask>=2.3.0 -hypothesis==5.19 -matplotlib>=2.0.0 -numpy>=1.9.2 -pandas>=0.18.1 -pathlib>=1.0.1 -prompt-toolkit>=1.0.7 +dask>=2.9.2 +hypothesis==5.19.3 +matplotlib>=3.0.3 +numpy>=1.17.4 +pandas>=1.0.0 +prompt-toolkit>=3.0 pyyaml>=3.01 -scipy>=0.19.0 -toolz>=0.8.0 -tqdm>=3.4.0 -dacite +scipy>=1.3.3 +tqdm>=4.39.0 From 7ef70e190092065eff62def2af424094e428dc84 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 31 Jul 2020 09:42:42 -0700 Subject: [PATCH 097/121] Add deadline=None to another test --- axelrod/tests/integration/test_matches.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axelrod/tests/integration/test_matches.py b/axelrod/tests/integration/test_matches.py index 79f9a0c44..d46ded024 100644 --- a/axelrod/tests/integration/test_matches.py +++ b/axelrod/tests/integration/test_matches.py @@ -39,7 +39,7 @@ def test_outcome_repeats(self, strategies, turns): turns=integers(min_value=1, max_value=20), seed=integers(min_value=0, max_value=4294967295), ) - @settings(max_examples=5) + @settings(max_examples=5, deadline=None) def test_outcome_repeats_stochastic(self, strategies, turns, seed): """a test to check that if a seed is set stochastic strategies give the same result""" From 40d9cb7f1224b9f6ad4a3ba1d479dc227c0fe07e Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 31 Jul 2020 10:00:52 -0700 Subject: [PATCH 098/121] Attempt 4 to make windows happy with uint64 random seed type --- axelrod/random_.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/axelrod/random_.py b/axelrod/random_.py index c2c3b3e4d..73b2f4092 100644 --- a/axelrod/random_.py +++ b/axelrod/random_.py @@ -26,7 +26,7 @@ def randint(self, *args, **kwargs): return self._random.randint(*args, **kwargs) def random_seed_int(self): - return self.randint(0, 2**32-1, dtype=np.int64) + return self.randint(low=0, high=2**32-1, dtype="uint64") def choice(self, *args, **kwargs): return self._random.choice(*args, **kwargs) @@ -131,7 +131,8 @@ def _fill_ints(self): self._ints = self._random_generator.randint( low=0, high=2**32 - 1, - size=self._batch_size) + size=self._batch_size, + dtype="uint64") self._index = 0 def __next__(self): From 9ea9f98fc2a84c50d711f53b9fdafca231ee44e1 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 31 Jul 2020 10:13:21 -0700 Subject: [PATCH 099/121] Add toolz back to requirements.txt for dask --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 5bb9ceeb8..e38389043 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,6 @@ pandas>=1.0.0 prompt-toolkit>=3.0 pyyaml>=3.01 scipy>=1.3.3 +# toolz required for dask +toolz>=0.10.0 tqdm>=4.39.0 From f8821fc35e42766e24e21ec643b6a3ddc83512f7 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 31 Jul 2020 10:26:05 -0700 Subject: [PATCH 100/121] Be more explicit about dask dependencies --- requirements.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index e38389043..f9ca34c6c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,8 @@ +# dask dependencies: https://docs.dask.org/en/latest/install.html +cloudpickle>=0.2.2 +fsspec>=0.6.0 +toolz>=0.8.2 + dask>=2.9.2 hypothesis==5.19.3 matplotlib>=3.0.3 @@ -6,6 +11,4 @@ pandas>=1.0.0 prompt-toolkit>=3.0 pyyaml>=3.01 scipy>=1.3.3 -# toolz required for dask -toolz>=0.10.0 tqdm>=4.39.0 From 398052aebad0fc7622fe63f259e28ebf86600627 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 31 Jul 2020 17:21:24 -0700 Subject: [PATCH 101/121] Add tests to improve coverage --- axelrod/strategies/meta.py | 3 ++ axelrod/tests/strategies/test_meta.py | 37 +++++++++++++++++++++++ axelrod/tests/strategies/test_player.py | 7 +++++ axelrod/tests/strategies/test_punisher.py | 3 +- 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/axelrod/strategies/meta.py b/axelrod/strategies/meta.py index f54a942ed..91936ceed 100644 --- a/axelrod/strategies/meta.py +++ b/axelrod/strategies/meta.py @@ -544,6 +544,9 @@ class MetaMixer(MetaPlayer): def __init__(self, team=None, distribution=None): super().__init__(team=team) + # Check that distribution is not all zeros, which will make numpy unhappy. + if distribution and all(x == 0 for x in distribution): + distribution = None self.distribution = distribution def _post_init(self): diff --git a/axelrod/tests/strategies/test_meta.py b/axelrod/tests/strategies/test_meta.py index 8351ef080..6c41f8c91 100644 --- a/axelrod/tests/strategies/test_meta.py +++ b/axelrod/tests/strategies/test_meta.py @@ -88,6 +88,12 @@ def test_clone(self, seed): self.assertEqual(len(player1.history), turns) self.assertEqual(player1.history, player2.history) + def test_update_histories(self): + """Artificial test to ensure that an exception is thrown.""" + p = axl.MetaHunter() + with self.assertRaises(TypeError): + p.update_histories(C) + class TestMetaMajority(TestMetaPlayer): name = "Meta Majority" @@ -150,6 +156,15 @@ class TestMetaWinnerEnsemble(TestMetaPlayer): name = "Meta Winner Ensemble" player = axl.MetaWinnerEnsemble + def test_singularity(self): + """Test meta_strategy when the player is singular.""" + team = [axl.Cooperator] + player = axl.MetaWinnerEnsemble(team=team) + self.assertFalse(Classifiers["stochastic"](player)) + coplayer = axl.Defector() + match = axl.Match((player, coplayer), turns=10) + match.play() + def test_stochasticity(self): # One player teams may be stochastic or not team = [axl.Cooperator] @@ -548,6 +563,28 @@ def test_stochasticity(self): self.assertTrue(Classifiers["stochastic"](player)) def test_strategy(self): + # Distribution = None + team = [axl.TitForTat, axl.Cooperator, axl.Grudger] + distribution = None + + actions = [(C, C)] * 20 + self.versus_test( + opponent=axl.Cooperator(), + expected_actions=actions, + init_kwargs={"team": team, "distribution": distribution}, + ) + + # Distribution = [0, 0, 0] + team = [axl.TitForTat, axl.Cooperator, axl.Grudger] + distribution = [0, 0, 0] + + actions = [(C, C)] * 20 + self.versus_test( + opponent=axl.Cooperator(), + expected_actions=actions, + init_kwargs={"team": team, "distribution": distribution}, + ) + team = [axl.TitForTat, axl.Cooperator, axl.Grudger] distribution = [0.2, 0.5, 0.3] diff --git a/axelrod/tests/strategies/test_player.py b/axelrod/tests/strategies/test_player.py index fe37d466f..9fe3cb883 100644 --- a/axelrod/tests/strategies/test_player.py +++ b/axelrod/tests/strategies/test_player.py @@ -2,6 +2,7 @@ import pickle import types import unittest +import warnings import axelrod as axl import numpy as np @@ -56,6 +57,12 @@ class TestPlayerClass(unittest.TestCase): player = axl.Player classifier = {"stochastic": False} + def test_seed_warning(self): + """Test that the user is warned if a null seed is given.""" + player = self.Player() + with warnings.catch_warnings(): + player._set_seed(seed=None) + def test_play(self): player1, player2 = self.player(), self.player() player1.strategy = cooperate diff --git a/axelrod/tests/strategies/test_punisher.py b/axelrod/tests/strategies/test_punisher.py index 77a8db244..5e3320bd9 100644 --- a/axelrod/tests/strategies/test_punisher.py +++ b/axelrod/tests/strategies/test_punisher.py @@ -155,7 +155,7 @@ def test_strategy(self): class TestTrickyLevelPunisher(TestPlayer): name = "Level Punisher" - player = axl.LevelPunisher + player = axl.TrickyLevelPunisher expected_classifier = { "memory_depth": float("inf"), # Long memory "stochastic": False, @@ -192,3 +192,4 @@ def test_strategy(self): opponent = axl.MockPlayer([C] * 10) actions = [(C, C)] * 5 self.versus_test(opponent=opponent, expected_actions=actions) + From 2fad16a885a97903aa1a639fccc4aad435f02f1e Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 31 Jul 2020 18:26:14 -0700 Subject: [PATCH 102/121] Fix TricklyLevelPunisher tests --- axelrod/strategies/punisher.py | 4 +--- axelrod/tests/strategies/test_player.py | 10 ++++----- axelrod/tests/strategies/test_punisher.py | 25 +++++++++-------------- 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/axelrod/strategies/punisher.py b/axelrod/strategies/punisher.py index 76f7f32c9..262901675 100644 --- a/axelrod/strategies/punisher.py +++ b/axelrod/strategies/punisher.py @@ -133,9 +133,7 @@ class LevelPunisher(Player): def strategy(self, opponent: Player) -> Action: if len(opponent.history) < 10: return C - elif (len(opponent.history) - opponent.cooperations) / len( - opponent.history - ) > 0.2: + elif opponent.defections / len(opponent.history) > 0.2: return D else: return C diff --git a/axelrod/tests/strategies/test_player.py b/axelrod/tests/strategies/test_player.py index 9fe3cb883..92de5dd8f 100644 --- a/axelrod/tests/strategies/test_player.py +++ b/axelrod/tests/strategies/test_player.py @@ -59,7 +59,7 @@ class TestPlayerClass(unittest.TestCase): def test_seed_warning(self): """Test that the user is warned if a null seed is given.""" - player = self.Player() + player = self.player() with warnings.catch_warnings(): player._set_seed(seed=None) @@ -646,10 +646,10 @@ def versus_test( match.play() # Test expected sequence of plays from the match is as expected. - for (play, expected_play) in zip(player1.history, expected_actions1): - self.assertEqual(play, expected_play) - for (play, expected_play) in zip(player2.history, expected_actions2): - self.assertEqual(play, expected_play) + for i, (play, expected_play) in enumerate(zip(player1.history, expected_actions1)): + self.assertEqual((i, play), (i, expected_play)) + for i, (play, expected_play) in enumerate(zip(player2.history, expected_actions2)): + self.assertEqual((i, play), (i, expected_play)) # Test final player attributes are as expected if attrs: diff --git a/axelrod/tests/strategies/test_punisher.py b/axelrod/tests/strategies/test_punisher.py index 5e3320bd9..f2fabfb60 100644 --- a/axelrod/tests/strategies/test_punisher.py +++ b/axelrod/tests/strategies/test_punisher.py @@ -154,7 +154,7 @@ def test_strategy(self): class TestTrickyLevelPunisher(TestPlayer): - name = "Level Punisher" + name = "Tricky Level Punisher" player = axl.TrickyLevelPunisher expected_classifier = { "memory_depth": float("inf"), # Long memory @@ -171,25 +171,20 @@ def test_strategy(self): actions = [(C, C)] * 9 self.versus_test(opponent=axl.Cooperator(), expected_actions=actions) - # After 10 rounds # Check if number of defections by opponent is greater than 20% - opponent = axl.MockPlayer([C] * 4 + [D] * 2 + [C] * 3 + [D]) - actions = [(C, C)] * 4 + [(C, D)] * 2 + [(C, C)] * 3 + [(C, D), (D, C)] + op_actions = [C] * 6 + [D] * 4 + opponent = axl.MockPlayer(op_actions) + actions = list(zip([C] * 7 + [D] * 3, op_actions)) self.versus_test(opponent=opponent, expected_actions=actions) # Check if number of defections by opponent is greater than 10% - opponent = axl.MockPlayer([C] * 4 + [D] + [C] * 4 + [D]) - actions = [(C, C)] * 4 + [(C, D)] + [(C, C)] * 4 + [(C, D), (C, C)] + op_actions = [C] * 8 + [D, C] + opponent = axl.MockPlayer(op_actions) + actions = list(zip([C] * 9 + [D], op_actions)) self.versus_test(opponent=opponent, expected_actions=actions) - # After 10 rounds # Check if number of defections by opponent is greater than 5% - opponent = axl.MockPlayer([C] * 4 + [D] + [C] * 5) - actions = [(C, C)] * 4 + [(C, D)] + [(C, C)] * 5 + op_actions = [C] * 18 + [D, C] + opponent = axl.MockPlayer(op_actions) + actions = list(zip([C] * 19 + [D], op_actions)) self.versus_test(opponent=opponent, expected_actions=actions) - - # Check if number of defections by opponent is less than 5% - opponent = axl.MockPlayer([C] * 10) - actions = [(C, C)] * 5 - self.versus_test(opponent=opponent, expected_actions=actions) - From dcd9d9247ac262939bf50b199ece9be37adad979 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 31 Jul 2020 19:28:05 -0700 Subject: [PATCH 103/121] Fix seed warning test --- axelrod/tests/strategies/test_player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axelrod/tests/strategies/test_player.py b/axelrod/tests/strategies/test_player.py index 92de5dd8f..673a87379 100644 --- a/axelrod/tests/strategies/test_player.py +++ b/axelrod/tests/strategies/test_player.py @@ -61,7 +61,7 @@ def test_seed_warning(self): """Test that the user is warned if a null seed is given.""" player = self.player() with warnings.catch_warnings(): - player._set_seed(seed=None) + player.set_seed(seed=None) def test_play(self): player1, player2 = self.player(), self.player() From 08987ae0734f948a8d7ad0193098ed1b3ac6d8e8 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 31 Jul 2020 20:39:08 -0700 Subject: [PATCH 104/121] Fix a few more tests and cover some remaining lines --- axelrod/strategies/meta.py | 2 -- axelrod/tests/strategies/test_apavlov.py | 2 +- .../tests/strategies/test_evolvable_player.py | 5 +---- .../strategies/test_finite_state_machines.py | 7 +++++++ axelrod/tests/strategies/test_meta.py | 19 +++++++++++++++---- axelrod/tests/strategies/test_player.py | 2 +- axelrod/tests/strategies/test_rand.py | 2 +- 7 files changed, 26 insertions(+), 13 deletions(-) diff --git a/axelrod/strategies/meta.py b/axelrod/strategies/meta.py index 91936ceed..c5ba74a06 100644 --- a/axelrod/strategies/meta.py +++ b/axelrod/strategies/meta.py @@ -563,8 +563,6 @@ def _post_init(self): # deterministic, and we can avoid _random. if distribution: total = sum(distribution) - if total == 0: - return distribution = np.array(distribution) / total if 1 in distribution: self.index = list(distribution).index(1) diff --git a/axelrod/tests/strategies/test_apavlov.py b/axelrod/tests/strategies/test_apavlov.py index 3641a0d87..8d3288a93 100644 --- a/axelrod/tests/strategies/test_apavlov.py +++ b/axelrod/tests/strategies/test_apavlov.py @@ -73,7 +73,7 @@ def test_strategy_PavlovD(self): opponent, expected_actions=actions, attrs={"opponent_class": "PavlovD"} ) - def test_strategy_PavlovD(self): + def test_strategy_PavlovD2(self): """Tests that PavolvD is identified by DDCDDC and that the response is D then C""" opponent = axl.MockPlayer(actions=[D, D, C, D, D, C, D]) diff --git a/axelrod/tests/strategies/test_evolvable_player.py b/axelrod/tests/strategies/test_evolvable_player.py index 97cba8e9b..58d7e48d6 100644 --- a/axelrod/tests/strategies/test_evolvable_player.py +++ b/axelrod/tests/strategies/test_evolvable_player.py @@ -17,9 +17,6 @@ class PartialedClass(cls): seed = kwargs["seed"] except KeyError: kwargs["seed"] = 1 - else: - if seed is None: - kwargs["seed"] = 1 __init__ = functools.partialmethod( cls.__init__, **kwargs) @@ -65,7 +62,7 @@ def player(self, seed=1): if seed is None: raise Exception() params = self.init_parameters.copy() - if "seed" not in params: + if "seed" not in params: # pragma: no cover params["seed"] = seed return self.player_class(**params) diff --git a/axelrod/tests/strategies/test_finite_state_machines.py b/axelrod/tests/strategies/test_finite_state_machines.py index 55f21cd05..f3525b0e1 100644 --- a/axelrod/tests/strategies/test_finite_state_machines.py +++ b/axelrod/tests/strategies/test_finite_state_machines.py @@ -1094,6 +1094,13 @@ def test_create_vector_bounds(self): self.assertEqual(lb, [0] * (4 * num_states + 1)) self.assertEqual(ub, [1] * (4 * num_states + 1)) + def test_mutate(self): + """Test to trigger random lines in mutate""" + for seed in [18, 22]: + player = axl.EvolvableFSMPlayer( + num_states=4, mutation_probability=0.5, seed=seed) + player.mutate() + class TestEvolvableFSMPlayer2(TestEvolvablePlayer): name = "EvolvableFSMPlayer" diff --git a/axelrod/tests/strategies/test_meta.py b/axelrod/tests/strategies/test_meta.py index 6c41f8c91..93eca1090 100644 --- a/axelrod/tests/strategies/test_meta.py +++ b/axelrod/tests/strategies/test_meta.py @@ -732,21 +732,21 @@ def test_strategy(self): # Test TitForTat behavior in first 15 turns opponent = axl.Cooperator() actions = list([(C, C)]) * 15 - self.versus_test(opponent, expected_actions=actions) + self.versus_test(opponent, expected_actions=actions, seed=1) opponent = axl.Defector() actions = [(C, D)] + list([(D, D)]) * 14 - self.versus_test(opponent, expected_actions=actions) + self.versus_test(opponent, expected_actions=actions, seed=1) opponent = axl.Alternator() actions = [(C, C)] + [(C, D), (D, C)] * 7 - self.versus_test(opponent, expected_actions=actions) + self.versus_test(opponent, expected_actions=actions, seed=1) opponent_actions = [C, D, D, C, D, C, C, D, C, D, D, C, C, D, D] opponent = axl.MockPlayer(actions=opponent_actions) mem_actions = [C, C, D, D, C, D, C, C, D, C, D, D, C, C, D] actions = list(zip(mem_actions, opponent_actions)) - self.versus_test(opponent, expected_actions=actions) + self.versus_test(opponent, expected_actions=actions, seed=1) def test_strategy2(self): opponent = axl.Random() @@ -812,3 +812,14 @@ def test_alternative_starting_strategies(self): "start_strategy_duration": 0, }, ) + + def test_memory_alter_delete(self): + """Trigger memory_alter and memory_delete.""" + opponent = axl.Cooperator() + actions = list([(C, C)]) * 50 + self.versus_test( + opponent, + expected_actions=actions, + init_kwargs={"start_strategy": axl.Cooperator}, + seed=11 + ) diff --git a/axelrod/tests/strategies/test_player.py b/axelrod/tests/strategies/test_player.py index 673a87379..3e025cee1 100644 --- a/axelrod/tests/strategies/test_player.py +++ b/axelrod/tests/strategies/test_player.py @@ -656,7 +656,7 @@ def versus_test( for attr, value in attrs.items(): self.assertEqual(getattr(player1, attr), value) - def search_seeds(self, *args, **kwargs): + def search_seeds(self, *args, **kwargs): # pragma: no cover """Search for a seed that will pass the test. To use to find a new seed for a versus_test, change self.versus_test to self.search_seeds within a TestPlayer or TestMatch class. diff --git a/axelrod/tests/strategies/test_rand.py b/axelrod/tests/strategies/test_rand.py index 38066eb17..c9341ab87 100644 --- a/axelrod/tests/strategies/test_rand.py +++ b/axelrod/tests/strategies/test_rand.py @@ -34,7 +34,7 @@ def test_stochastic_behavior1(self): actions = [(C, C), (D, C), (D, C), (C, C)] self.versus_test(opponent, expected_actions=actions, seed=1) - def test_stochastic_behavior1(self): + def test_stochastic_behavior2(self): opponent = axl.MockPlayer() actions = [(D, C), (C, C), (D, C)] self.versus_test(opponent, expected_actions=actions, seed=2) From c547b56431585ca16806fa2761f735e3b15d27a6 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Fri, 31 Jul 2020 20:58:28 -0700 Subject: [PATCH 105/121] Remove leftover testing code. --- axelrod/tests/strategies/test_evolvable_player.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/axelrod/tests/strategies/test_evolvable_player.py b/axelrod/tests/strategies/test_evolvable_player.py index 58d7e48d6..caac37d10 100644 --- a/axelrod/tests/strategies/test_evolvable_player.py +++ b/axelrod/tests/strategies/test_evolvable_player.py @@ -59,8 +59,6 @@ class TestEvolvablePlayer(TestPlayer): init_parameters = dict() def player(self, seed=1): - if seed is None: - raise Exception() params = self.init_parameters.copy() if "seed" not in params: # pragma: no cover params["seed"] = seed From 853b652ff78031feab1781bc31ed63044b773b4f Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sat, 1 Aug 2020 08:25:56 -0700 Subject: [PATCH 106/121] Fix doctest output --- docs/tutorials/advanced/tournament_results.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/advanced/tournament_results.rst b/docs/tutorials/advanced/tournament_results.rst index f2778ebfa..72ca2e4a8 100644 --- a/docs/tutorials/advanced/tournament_results.rst +++ b/docs/tutorials/advanced/tournament_results.rst @@ -343,7 +343,7 @@ This gives the count of cooperations made by each player during the first turn of every match:: >>> results.initial_cooperation_count - [9.0, 0.0, 9.0, 9.0] + [9, 0, 9, 9] Each player plays an opponent a total of 9 times (3 opponents and 3 repetitions). Apart from the :code:`Defector`, they all cooperate on the first From 2e0e66363c88b6b7e44f13efc1dff33b91b33f58 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sat, 1 Aug 2020 09:04:11 -0700 Subject: [PATCH 107/121] Fix type annotations in hmm.py --- axelrod/strategies/hmm.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/axelrod/strategies/hmm.py b/axelrod/strategies/hmm.py index 6ef2e0093..35b6e85a8 100644 --- a/axelrod/strategies/hmm.py +++ b/axelrod/strategies/hmm.py @@ -1,3 +1,4 @@ +from typing import Any, Dict from axelrod.action import Action from axelrod.evolvable_player import ( EvolvablePlayer, @@ -71,9 +72,11 @@ def __init__( self.transitions_D = transitions_D self.emission_probabilities = emission_probabilities self.state = initial_state - self._cache_C = dict() - self._cache_D = dict() + self._cache_C = dict() # type: Dict[int, int] + self._cache_D = dict() # type: Dict[int, int] self._cache_deterministic_transitions() + # Random generator will be set by parent strategy + self._random = None # type: Any def _cache_deterministic_transitions(self): """Cache deterministic transitions to avoid unnecessary random draws.""" @@ -217,6 +220,7 @@ def set_seed(self, seed=None): super().set_seed(seed=seed) # Share RNG with HMM # The evolvable version of the class needs to manually share the rng with the HMM. + # after initialization. try: self.hmm._random = self._random except AttributeError: From 761f88786c63e061b4b785bcc75ec231332cbf53 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sat, 1 Aug 2020 09:08:26 -0700 Subject: [PATCH 108/121] Run isort --- axelrod/classifier.py | 2 +- axelrod/strategies/hmm.py | 1 + axelrod/strategies/rand.py | 2 -- axelrod/tests/strategies/test_hmm.py | 1 - axelrod/tests/strategies/test_memoryone.py | 2 +- axelrod/tests/strategies/test_memorytwo.py | 3 +-- axelrod/tests/strategies/test_player.py | 1 - axelrod/tests/unit/test_strategy_transformers.py | 2 +- 8 files changed, 5 insertions(+), 9 deletions(-) diff --git a/axelrod/classifier.py b/axelrod/classifier.py index 267d94df8..44edf510c 100644 --- a/axelrod/classifier.py +++ b/axelrod/classifier.py @@ -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" diff --git a/axelrod/strategies/hmm.py b/axelrod/strategies/hmm.py index 35b6e85a8..393d3b2a3 100644 --- a/axelrod/strategies/hmm.py +++ b/axelrod/strategies/hmm.py @@ -1,4 +1,5 @@ from typing import Any, Dict + from axelrod.action import Action from axelrod.evolvable_player import ( EvolvablePlayer, diff --git a/axelrod/strategies/rand.py b/axelrod/strategies/rand.py index 9c7cff8f0..499790acf 100644 --- a/axelrod/strategies/rand.py +++ b/axelrod/strategies/rand.py @@ -1,7 +1,6 @@ from axelrod.action import Action from axelrod.player import Player - C, D = Action.C, Action.D @@ -61,4 +60,3 @@ def cooperate(cls, opponent: Player) -> Action: @classmethod def defect(cls, opponent: Player) -> Action: return D - diff --git a/axelrod/tests/strategies/test_hmm.py b/axelrod/tests/strategies/test_hmm.py index 0741e0b3e..e60d4e3dd 100644 --- a/axelrod/tests/strategies/test_hmm.py +++ b/axelrod/tests/strategies/test_hmm.py @@ -13,7 +13,6 @@ from .test_evolvable_player import PartialClass, TestEvolvablePlayer from .test_player import TestMatch, TestPlayer - C, D = axl.Action.C, axl.Action.D diff --git a/axelrod/tests/strategies/test_memoryone.py b/axelrod/tests/strategies/test_memoryone.py index d1ca82403..01e720618 100644 --- a/axelrod/tests/strategies/test_memoryone.py +++ b/axelrod/tests/strategies/test_memoryone.py @@ -5,10 +5,10 @@ import axelrod as axl from axelrod.strategies.memoryone import MemoryOnePlayer -from .test_player import TestPlayer, test_four_vector from .test_alternator import TestAlternator from .test_cooperator import TestCooperator from .test_defector import TestDefector +from .test_player import TestPlayer, test_four_vector from .test_titfortat import TestTitForTat C, D = axl.Action.C, axl.Action.D diff --git a/axelrod/tests/strategies/test_memorytwo.py b/axelrod/tests/strategies/test_memorytwo.py index 2f6a6529c..0125ef0e9 100644 --- a/axelrod/tests/strategies/test_memorytwo.py +++ b/axelrod/tests/strategies/test_memorytwo.py @@ -6,10 +6,10 @@ import axelrod as axl from axelrod.strategies.memorytwo import MemoryTwoPlayer -from .test_player import TestPlayer from .test_alternator import TestAlternator from .test_cooperator import TestCooperator from .test_defector import TestDefector +from .test_player import TestPlayer C, D = axl.Action.C, axl.Action.D @@ -304,4 +304,3 @@ class TestMemoryTwoAlternator(TestAlternator): "manipulates_source": False, "manipulates_state": False, } - diff --git a/axelrod/tests/strategies/test_player.py b/axelrod/tests/strategies/test_player.py index 3e025cee1..29ef1799b 100644 --- a/axelrod/tests/strategies/test_player.py +++ b/axelrod/tests/strategies/test_player.py @@ -7,7 +7,6 @@ import axelrod as axl import numpy as np from axelrod.tests.property import strategy_lists - from hypothesis import given, settings from hypothesis.strategies import integers, sampled_from diff --git a/axelrod/tests/unit/test_strategy_transformers.py b/axelrod/tests/unit/test_strategy_transformers.py index 1a3db576b..adaa05d1c 100644 --- a/axelrod/tests/unit/test_strategy_transformers.py +++ b/axelrod/tests/unit/test_strategy_transformers.py @@ -2,8 +2,8 @@ from axelrod.strategy_transformers import * from axelrod.tests.strategies.test_cooperator import TestCooperator from axelrod.tests.strategies.test_defector import TestDefector -from axelrod.tests.strategies.test_titfortat import TestTitForTat from axelrod.tests.strategies.test_player import TestMatch, TestPlayer +from axelrod.tests.strategies.test_titfortat import TestTitForTat C, D = axl.Action.C, axl.Action.D From 1fd9dd6ecbdbfe252dfe539cd26d77149c6c4ce7 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sat, 1 Aug 2020 12:00:46 -0700 Subject: [PATCH 109/121] Rework classifiers for transformers, rebuild expected classifiers to correctly capture Meta player makes_use_of --- axelrod/data/all_classifiers.yml | 45 +++++++++++++++++++------ axelrod/makes_use_of.py | 13 +++++-- axelrod/strategy_transformers.py | 24 ++++++------- axelrod/tests/unit/test_makes_use_of.py | 24 ++++++++++++- 4 files changed, 78 insertions(+), 28 deletions(-) diff --git a/axelrod/data/all_classifiers.yml b/axelrod/data/all_classifiers.yml index f028f5ab4..ea52f2351 100644 --- a/axelrod/data/all_classifiers.yml +++ b/axelrod/data/all_classifiers.yml @@ -961,7 +961,9 @@ 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 @@ -969,7 +971,8 @@ Meta Majority: 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 @@ -977,7 +980,9 @@ Meta Majority Finite Memory: 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 @@ -985,7 +990,8 @@ Meta Majority Long Memory: 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 @@ -993,7 +999,9 @@ Meta Majority Memory One: 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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -1130,8 +1143,9 @@ 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 @@ -1139,7 +1153,8 @@ NMWE Deterministic: 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 @@ -1147,7 +1162,9 @@ NMWE Finite Memory: 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 @@ -1155,7 +1172,8 @@ NMWE Long Memory: 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 @@ -1163,7 +1181,9 @@ NMWE Memory One: 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 @@ -1197,6 +1217,7 @@ Nice Meta Winner: long_run_time: true makes_use_of: !!set game: null + length: null manipulates_source: false manipulates_state: false memory_depth: .inf @@ -1204,7 +1225,9 @@ Nice Meta Winner: 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 diff --git a/axelrod/makes_use_of.py b/axelrod/makes_use_of.py index 41356123c..392f25809 100644 --- a/axelrod/makes_use_of.py +++ b/axelrod/makes_use_of.py @@ -16,7 +16,11 @@ def method_makes_use_of(method: Callable) -> Set[Text]: def class_makes_use_of(cls) -> Set[Text]: - result = set() + try: + result = cls.classifier["makes_use_of"] + except (AttributeError, KeyError): + result = set() + for method in inspect.getmembers(cls, inspect.ismethod): if method[0] == "__init__": continue @@ -32,10 +36,13 @@ def makes_use_of(player: Type[Player]) -> Set[Text]: def makes_use_of_variant( - player_or_method: Union[Callable, Type[Player]], verbose=False + player_or_method: Union[Callable, Type[Player]] ) -> Set[Text]: """A version of makes_use_of that works on functions or player classes.""" try: return method_makes_use_of(player_or_method) - except: + # OSError catches the case of a transformed player, which has a dynamically + # created class. + # TypeError is the case in which we have a class rather than a method + except (OSError, TypeError): return class_makes_use_of(player_or_method) diff --git a/axelrod/strategy_transformers.py b/axelrod/strategy_transformers.py index 09c0a71c9..a7297382d 100644 --- a/axelrod/strategy_transformers.py +++ b/axelrod/strategy_transformers.py @@ -5,13 +5,14 @@ See the various Meta strategies for another type of transformation. """ +import inspect from importlib import import_module from typing import Any from axelrod.strategies.sequence_player import SequencePlayer from .action import Action -from .makes_use_of import * +from .makes_use_of import makes_use_of_variant from .player import Player C, D = Action.C, Action.D @@ -25,8 +26,11 @@ def makes_use_of_reclassifier(original_classifier, player_class, wrapper): """Reclassifier for post-transformation determination of whether strategy makes_use_of anything differently.""" - classifier_makes_use_of = makes_use_of(player_class) + classifier_makes_use_of = makes_use_of_variant(player_class) + # Wrapper is usually a function, but can be a class, e.g. in the case of the + # RetaliationUntilApologyWrapper classifier_makes_use_of.update(makes_use_of_variant(wrapper)) + try: original_classifier["makes_use_of"].update(classifier_makes_use_of) except KeyError: @@ -156,16 +160,6 @@ def strategy(self, opponent): # Modify the Player name (class variable inherited from Player) name = " ".join([name_prefix, PlayerClass.name]) - # original_classifier = copy.deepcopy(PlayerClass.classifier) # Copy - # if reclassifier is not None: - # classifier = reclassifier(original_classifier, *args, **kwargs) - # else: - # classifier = original_classifier - # classifier_makes_use_of = makes_use_of(PlayerClass) - # classifier_makes_use_of.update( - # makes_use_of_variant(strategy_wrapper)) - # classifier["makes_use_of"] = classifier_makes_use_of - # Define the new __repr__ method to add the wrapper arguments # at the end of the name def __repr__(self): @@ -494,10 +488,14 @@ def final_sequence(player, opponent, action, seq): def final_reclassifier(original_classifier, seq): - """Reclassify the strategy""" + """Reclassify the strategy.""" original_classifier["memory_depth"] = max( len(seq), original_classifier["memory_depth"] ) + # This should also be picked up by the makes_use_of inspection, + # but we list it here to be explicit. + if len(seq) > 0: + original_classifier["makes_use_of"].add("length") return original_classifier diff --git a/axelrod/tests/unit/test_makes_use_of.py b/axelrod/tests/unit/test_makes_use_of.py index 5f41b06de..236572cee 100644 --- a/axelrod/tests/unit/test_makes_use_of.py +++ b/axelrod/tests/unit/test_makes_use_of.py @@ -3,7 +3,13 @@ import unittest import axelrod as axl -from axelrod.makes_use_of import makes_use_of +from axelrod.makes_use_of import ( + class_makes_use_of, + makes_use_of, + makes_use_of_variant, + method_makes_use_of +) +from axelrod.strategy_transformers import final_sequence class TestMakesUseOfLengthAndGamePlayer(axl.Player): @@ -45,3 +51,19 @@ def test_makes_use_of_length_and_game(self): def test_makes_use_of_empty(self): self.assertEqual(makes_use_of(TestMakesUseOfNothingPlayer()), set()) + + def test_untransformed_class(self): + for player in [axl.Cooperator(), axl.Random()]: + self.assertEqual(class_makes_use_of(player), set()) + self.assertEqual(makes_use_of_variant(player), set()) + self.assertEqual(method_makes_use_of(player.strategy), set()) + + def test_transformer_wrapper(self): + # Test that the final transformer wrapper makes use of length + self.assertEqual(method_makes_use_of(final_sequence), {"length"}) + + def test_makes_use_of_transformed(self): + # These players use match length via Final transformer + for player in [axl.BackStabber(), axl.FirstBySteinAndRapoport()]: + self.assertEqual(makes_use_of(player), {"length"}) + self.assertEqual(makes_use_of_variant(player), {"length"}) From 7c233a79029f0ce106dccbd5d6c025d74ab8c388 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sat, 1 Aug 2020 12:16:44 -0700 Subject: [PATCH 110/121] Mark line as no cover --- axelrod/makes_use_of.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axelrod/makes_use_of.py b/axelrod/makes_use_of.py index 392f25809..cd6bc16d9 100644 --- a/axelrod/makes_use_of.py +++ b/axelrod/makes_use_of.py @@ -29,7 +29,7 @@ def class_makes_use_of(cls) -> Set[Text]: def makes_use_of(player: Type[Player]) -> Set[Text]: - if not isinstance(player, Player): + if not isinstance(player, Player): # pragma: no cover player = player() return class_makes_use_of(player) From 96c3352db92bc334fda07cd0f3257965aecf160b Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sat, 1 Aug 2020 12:38:42 -0700 Subject: [PATCH 111/121] Fix no-op isort command in github checks --- .github/workflows/config.yml | 2 +- axelrod/tests/unit/test_makes_use_of.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 553f4a0d2..f7180787e 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -50,7 +50,7 @@ jobs: - name: Check imports are sorted run: | python -m pip install isort - python -m isort --check-only + python -m isort --check-only --recursive axelrod/. - name: Check that all strategies are indexed run: | python run_strategy_indexer.py diff --git a/axelrod/tests/unit/test_makes_use_of.py b/axelrod/tests/unit/test_makes_use_of.py index 236572cee..ac5e6c1c7 100644 --- a/axelrod/tests/unit/test_makes_use_of.py +++ b/axelrod/tests/unit/test_makes_use_of.py @@ -7,7 +7,7 @@ class_makes_use_of, makes_use_of, makes_use_of_variant, - method_makes_use_of + method_makes_use_of, ) from axelrod.strategy_transformers import final_sequence From 3eac7c274d35ee4ec027caf7964f05c58bb7b733 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sat, 1 Aug 2020 13:04:30 -0700 Subject: [PATCH 112/121] Pin specific version of isort --- .github/workflows/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index f7180787e..62f0c4712 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -49,7 +49,7 @@ jobs: python run_mypy.py - name: Check imports are sorted run: | - python -m pip install isort + python -m pip install "isort==4.3.21" python -m isort --check-only --recursive axelrod/. - name: Check that all strategies are indexed run: | From c37fd741e70976abc43c6b2d7e2280497d79c985 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sat, 1 Aug 2020 13:40:13 -0700 Subject: [PATCH 113/121] Cleanup some comments --- axelrod/strategies/axelrod_first.py | 1 - axelrod/strategies/axelrod_second.py | 2 +- axelrod/strategies/finite_state_machines.py | 1 - axelrod/strategies/hmm.py | 2 +- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/axelrod/strategies/axelrod_first.py b/axelrod/strategies/axelrod_first.py index 24a71a254..0cc16d4e6 100644 --- a/axelrod/strategies/axelrod_first.py +++ b/axelrod/strategies/axelrod_first.py @@ -513,7 +513,6 @@ def __init__(self, p: float = 0.9) -> None: or (D, C), i.e. the opponent cooperated. """ four_vector = (p, 0, p, 0) - # self.p = p super().__init__(four_vector) diff --git a/axelrod/strategies/axelrod_second.py b/axelrod/strategies/axelrod_second.py index 54f772ff4..4f536f9b5 100644 --- a/axelrod/strategies/axelrod_second.py +++ b/axelrod/strategies/axelrod_second.py @@ -742,7 +742,7 @@ class SecondByCave(Player): - turn > 19 and percent defects > 0.79 Otherwise, respond to cooperation with cooperation. And respond to defections - with either a defectection (if opponent has defected at least 18 times) or with + with either a defection (if opponent has defected at least 18 times) or with a random (50/50) choice. [Cooperate on first.] Names: diff --git a/axelrod/strategies/finite_state_machines.py b/axelrod/strategies/finite_state_machines.py index c04b57272..2c7178f4f 100644 --- a/axelrod/strategies/finite_state_machines.py +++ b/axelrod/strategies/finite_state_machines.py @@ -160,7 +160,6 @@ def __init__( transitions=transitions, initial_state=initial_state, initial_action=initial_action) - # EvolvablePlayer.__init__(self) self.mutation_probability = mutation_probability self.overwrite_init_kwargs( transitions=transitions, diff --git a/axelrod/strategies/hmm.py b/axelrod/strategies/hmm.py index 393d3b2a3..ad64ddb55 100644 --- a/axelrod/strategies/hmm.py +++ b/axelrod/strategies/hmm.py @@ -220,7 +220,7 @@ def strategy(self, opponent: Player) -> Action: def set_seed(self, seed=None): super().set_seed(seed=seed) # Share RNG with HMM - # The evolvable version of the class needs to manually share the rng with the HMM. + # The evolvable version of the class needs to manually share the rng with the HMM # after initialization. try: self.hmm._random = self._random From 23b1d504984de5c9dd6824e1d6bcfb4642a20fbd Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sat, 1 Aug 2020 14:13:00 -0700 Subject: [PATCH 114/121] Find seed for original doctest --- docs/reference/overview_of_strategies.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/overview_of_strategies.rst b/docs/reference/overview_of_strategies.rst index 391b38052..ce30d7298 100644 --- a/docs/reference/overview_of_strategies.rst +++ b/docs/reference/overview_of_strategies.rst @@ -428,13 +428,13 @@ The following code reproduces the above:: ... axl.WinStayLoseShift(), ... ] >>> turns = 1000 - >>> tournament = axl.Tournament(players, turns=turns, repetitions=1, seed=1) + >>> tournament = axl.Tournament(players, turns=turns, repetitions=1, seed=75) >>> results = tournament.play(progress_bar=False) >>> for average_score_per_turn in results.payoff_matrix[-2]: ... print(round(average_score_per_turn * turns, 1)) 3000.0 915.0 - 2756.0 + 2763.0 3000.0 3000.0 2219.0 From 1e4ee6910a64398852c169457409b5e5875e6487 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Tue, 4 Aug 2020 08:02:17 -0700 Subject: [PATCH 115/121] Remove seeding from filter integration test --- axelrod/tests/integration/test_filtering.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/axelrod/tests/integration/test_filtering.py b/axelrod/tests/integration/test_filtering.py index 4b0d9d2c2..b76161bf9 100644 --- a/axelrod/tests/integration/test_filtering.py +++ b/axelrod/tests/integration/test_filtering.py @@ -96,16 +96,13 @@ def test_memory_depth_filtering( strategies=strategy_lists(min_size=20, max_size=20), ) @settings(max_examples=5, deadline=None) - def test_makes_use_of_filtering(self, seed_, strategies): + def test_makes_use_of_filtering(self, strategies): """ Test equivalent filtering using two approaches. - - This needs to be seeded as some players classification is random. """ classifiers = [["game"], ["length"], ["game", "length"]] for classifier in classifiers: - axl._module_random.seed(seed_) comprehension = set( [ s @@ -114,7 +111,6 @@ def test_makes_use_of_filtering(self, seed_, strategies): ] ) - axl._module_random.seed(seed_) filterset = {"makes_use_of": classifier} filtered = set(axl.filtered_strategies(filterset, strategies=strategies)) From 71634979521bcb0d2ec12a3c239f96f73901bb10 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Tue, 4 Aug 2020 08:06:17 -0700 Subject: [PATCH 116/121] Fix docstrings, remove comments, and update a few tests based on round 1 of review --- axelrod/tests/integration/test_filtering.py | 4 +--- axelrod/tests/strategies/test_calculator.py | 8 ++++---- axelrod/tests/strategies/test_human.py | 3 --- axelrod/tests/strategies/test_memorytwo.py | 7 +++---- axelrod/tests/strategies/test_meta.py | 5 ++--- axelrod/tests/strategies/test_player.py | 4 ++-- axelrod/tests/strategies/test_titfortat.py | 10 +++++----- 7 files changed, 17 insertions(+), 24 deletions(-) diff --git a/axelrod/tests/integration/test_filtering.py b/axelrod/tests/integration/test_filtering.py index b76161bf9..17a57243c 100644 --- a/axelrod/tests/integration/test_filtering.py +++ b/axelrod/tests/integration/test_filtering.py @@ -92,9 +92,7 @@ def test_memory_depth_filtering( filtered = set(axl.filtered_strategies(filterset, strategies=strategies)) self.assertEqual(comprehension, filtered) - @given(seed_=integers(min_value=0, max_value=4294967295), - strategies=strategy_lists(min_size=20, max_size=20), - ) + @given(strategies=strategy_lists(min_size=20, max_size=20)) @settings(max_examples=5, deadline=None) def test_makes_use_of_filtering(self, strategies): """ diff --git a/axelrod/tests/strategies/test_calculator.py b/axelrod/tests/strategies/test_calculator.py index 199bf69da..0d5eedfb3 100644 --- a/axelrod/tests/strategies/test_calculator.py +++ b/axelrod/tests/strategies/test_calculator.py @@ -23,7 +23,7 @@ class TestCalculator(TestPlayer): } def test_twenty_rounds_joss_for_cyclers(self): - """Uses axelrod.strategies.axelrod_first.Joss strategy for first 20 rounds""" + """Uses axelrod.strategies.axelrod_first.FirstByJoss strategy for first 20 rounds""" seed = 4 match = axl.Match((axl.FirstByJoss(), axl.Alternator()), turns=20, seed=seed) match.play() @@ -32,7 +32,7 @@ def test_twenty_rounds_joss_for_cyclers(self): ) def test_twenty_rounds_joss_then_defects_for_cyclers(self): - """Uses axelrod.strategies.axelrod_first.Joss strategy for first 20 rounds""" + """Uses axelrod.strategies.axelrod_first.FirstByJoss strategy for first 20 rounds""" seed = 4 match = axl.Match((axl.FirstByJoss(), axl.Alternator()), turns=20, seed=seed) match.play() @@ -42,7 +42,7 @@ def test_twenty_rounds_joss_then_defects_for_cyclers(self): ) def test_twenty_rounds_joss_for_noncyclers(self): - """Uses axelrod.strategies.axelrod_first.Joss strategy for first 20 rounds""" + """Uses axelrod.strategies.axelrod_first.FirstByJoss strategy for first 20 rounds""" seed = 4 match = axl.Match((axl.FirstByJoss(), axl.AntiCycler()), turns=20, seed=seed) match.play() @@ -51,7 +51,7 @@ def test_twenty_rounds_joss_for_noncyclers(self): ) def test_twenty_rounds_joss_then_tft_for_noncyclers(self): - """Uses axelrod.strategies.axelrod_first.Joss strategy for first 20 rounds""" + """Uses axelrod.strategies.axelrod_first.FirstByJoss strategy for first 20 rounds""" seed = 4 match = axl.Match((axl.FirstByJoss(), axl.AntiCycler()), turns=20, seed=seed) match.play() diff --git a/axelrod/tests/strategies/test_human.py b/axelrod/tests/strategies/test_human.py index e43817e0c..e0e2ca811 100644 --- a/axelrod/tests/strategies/test_human.py +++ b/axelrod/tests/strategies/test_human.py @@ -130,6 +130,3 @@ def test_repr(self): def equality_of_players_test(self, p1, p2, seed, opponent): return True - - def test_reproducibility_of_play(self): - return True diff --git a/axelrod/tests/strategies/test_memorytwo.py b/axelrod/tests/strategies/test_memorytwo.py index 0125ef0e9..339aef728 100644 --- a/axelrod/tests/strategies/test_memorytwo.py +++ b/axelrod/tests/strategies/test_memorytwo.py @@ -255,7 +255,7 @@ def test_strategy(self): class TestMemoryTwoCooperator(TestCooperator): - """Cooperator is equivalent to MemoryOnePlayer((1, 1, 1, 1), C)""" + """Cooperator is equivalent to MemoryTwoPlayer((1, 1, ..., 1), C)""" name = "Generic Memory Two Player: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], (C, C)" player = lambda x: axl.MemoryTwoPlayer(sixteen_vector=[1] * 16, initial=(C, C)) expected_classifier = { @@ -270,7 +270,7 @@ class TestMemoryTwoCooperator(TestCooperator): class TestMemoryTwoDefector(TestDefector): - """Defector is equivalent to MemoryOnePlayer((0, 0, 0, 0), D)""" + """Defector is equivalent to MemoryTwoPlayer((0, 0, ..., 0), D)""" name = "Generic Memory Two Player: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], (D, D)" player = lambda x: axl.MemoryTwoPlayer(sixteen_vector=[0] * 16, initial=(D, D)) expected_classifier = { @@ -291,10 +291,9 @@ def four_vector_to_sixteen_vector(four_vector): class TestMemoryTwoAlternator(TestAlternator): - """Alternator is equivalent to MemoryOnePlayer((0, 0, 1, 1), C)""" + """Alternator is equivalent to MemoryTwoPlayer(0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1), C).""" name = "Generic Memory Two Player: [0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1], (C, D)" player = lambda x: axl.MemoryTwoPlayer(sixteen_vector=four_vector_to_sixteen_vector((0, 0, 1, 1)), initial=(C, D)) - # player = lambda x: axl.MemoryTwoPlayer(sixteen_vector=four_vector_to_sixteen_vector((1, 1, 0, 0)), initial=(C, D)) expected_classifier = { "memory_depth": 1, "stochastic": False, diff --git a/axelrod/tests/strategies/test_meta.py b/axelrod/tests/strategies/test_meta.py index 93eca1090..6ec22db6b 100644 --- a/axelrod/tests/strategies/test_meta.py +++ b/axelrod/tests/strategies/test_meta.py @@ -180,9 +180,8 @@ def test_stochasticity(self): player = axl.MetaWinnerEnsemble(team=team) self.assertTrue(Classifiers["stochastic"](player)) - # If the players are all identical, a multiplayer - # team might is in fact deterministic, even though random values - # are being drawn. + # If the players are all identical, a multiplayer team might in fact + # be deterministic, even though random values are being drawn. team = [axl.Cooperator, axl.Cooperator] player = axl.MetaWinnerEnsemble(team=team) self.assertFalse(Classifiers["stochastic"](player)) diff --git a/axelrod/tests/strategies/test_player.py b/axelrod/tests/strategies/test_player.py index 29ef1799b..ec9fe051c 100644 --- a/axelrod/tests/strategies/test_player.py +++ b/axelrod/tests/strategies/test_player.py @@ -656,8 +656,8 @@ def versus_test( self.assertEqual(getattr(player1, attr), value) def search_seeds(self, *args, **kwargs): # pragma: no cover - """Search for a seed that will pass the test. To use to find a new seed - for a versus_test, change self.versus_test to self.search_seeds + """Search for a seed that will pass the test. Use to find a new seed + for a versus_test by changing self.versus_test to self.search_seeds within a TestPlayer or TestMatch class. """ for seed in range(1, 100000): diff --git a/axelrod/tests/strategies/test_titfortat.py b/axelrod/tests/strategies/test_titfortat.py index 39fe19c3e..fb984ea4b 100644 --- a/axelrod/tests/strategies/test_titfortat.py +++ b/axelrod/tests/strategies/test_titfortat.py @@ -669,7 +669,7 @@ def test_output_from_literature(self): ] turns = 1000 - tournament = axl.Tournament(players, turns=turns, repetitions=1, seed=1) + tournament = axl.Tournament(players, turns=turns, repetitions=1, seed=75) results = tournament.play(progress_bar=False) scores = [ round(average_score_per_turn * 1000, 1) @@ -678,7 +678,7 @@ def test_output_from_literature(self): expected_scores = [ 3000.0, 915.0, - 2756.0, + 2763.0, 3000.0, 3000.0, 2219.0, @@ -1031,7 +1031,7 @@ class Test1TitsFor1TatsIsTFT(TestTitForTat): class Test1TitsFor2TatsIsTF2T(TestTitFor2Tats): - """Tests that for N = 1 = M, all the TFT tests are passed.""" + """Tests that for N = 1, M = 2, all the TF2T tests are passed.""" name = "N Tit(s) For M Tat(s): 1, 2" player = lambda x: axl.NTitsForMTats(1, 2) expected_classifier = { @@ -1045,8 +1045,8 @@ class Test1TitsFor2TatsIsTF2T(TestTitFor2Tats): } -class Test2TitsFor1TatsIsT2FT(TestTwoTitsForTat): - """Tests that for N = 1 = M, all the TFT tests are passed.""" +class Test2TitsFor1TatsIs2TFT(TestTwoTitsForTat): + """Tests that for N = 2, M = 1, all the 2TFT tests are passed.""" name = "N Tit(s) For M Tat(s): 2, 1" player = lambda x: axl.NTitsForMTats(2, 1) expected_classifier = { From 57d403423bd739e9712566609f3ce0fb8b469e0a Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Wed, 5 Aug 2020 07:39:16 -0700 Subject: [PATCH 117/121] Updates following Nik's initial review --- axelrod/player.py | 4 ++-- axelrod/random_.py | 10 ++++++---- axelrod/tests/strategies/test_worse_and_worse.py | 5 ++--- .../running_axelrods_first_tournament.rst | 7 ++++--- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/axelrod/player.py b/axelrod/player.py index 1316c018d..496e9fecf 100644 --- a/axelrod/player.py +++ b/axelrod/player.py @@ -190,8 +190,8 @@ def set_seed(self, seed): """Set a random seed for the player's random number generator.""" if seed is None: warnings.warn( - "Initializing player with seed from Axelrod module random number generator." - " Results may not be seed reproducible.") + "Initializing player with seed from Axelrod module random number generator. " + "Results may not be seed reproducible.") self._seed = _module_random.random_seed_int() else: self._seed = seed diff --git a/axelrod/random_.py b/axelrod/random_.py index 73b2f4092..1197a097b 100644 --- a/axelrod/random_.py +++ b/axelrod/random_.py @@ -1,7 +1,9 @@ +from typing import Optional import numpy as np from axelrod.action import Action from numpy.random import RandomState + C, D = Action.C, Action.D @@ -9,13 +11,13 @@ class RandomGenerator(object): """Container around a random number generator. Enables reproducibility of player behavior, matches, and tournaments.""" - def __init__(self, seed=None): + def __init__(self, seed: Optional[int] = None): # _random is the internal object that generators random values self._random = RandomState() self.original_seed = seed self.seed(seed) - def seed(self, seed_): + def seed(self, seed_: Optional[int] = None): """Sets a seed""" self._random.seed(seed_) @@ -25,7 +27,7 @@ def random(self, *args, **kwargs): def randint(self, *args, **kwargs): return self._random.randint(*args, **kwargs) - def random_seed_int(self): + def random_seed_int(self) -> int: return self.randint(low=0, high=2**32-1, dtype="uint64") def choice(self, *args, **kwargs): @@ -117,7 +119,7 @@ class BulkRandomGenerator(object): """Bulk generator of random integers for tournament seeding and reproducibility. Bulk generation of random values is more efficient. Use this class like a generator.""" - def __init__(self, seed=None, batch_size=1000): + def __init__(self, seed=None, batch_size:int = 1000): self._random_generator = RandomState() self._random_generator.seed(seed) self._ints = None diff --git a/axelrod/tests/strategies/test_worse_and_worse.py b/axelrod/tests/strategies/test_worse_and_worse.py index f5ab0943c..1fd2d5ef8 100644 --- a/axelrod/tests/strategies/test_worse_and_worse.py +++ b/axelrod/tests/strategies/test_worse_and_worse.py @@ -22,13 +22,12 @@ class TestWorseAndWorse(TestPlayer): } def test_strategy(self): - """Test that the strategy gives expected behaviour.""" - # 6 Rounds Cooperate given seed + # For given seed cooperates for 6 rounds and then defects for a round actions = [(C, C)] * 6 + [(D, C)] + [(C, C)] * 3 self.versus_test(axl.Cooperator(), expected_actions=actions, seed=144) def test_strategy2(self): - # 6 Rounds Cooperate and Defect no matter opponent + # Test that behaviour does not depend on opponent actions = [(C, D)] * 6 + [(D, D)] + [(C, D)] * 3 self.versus_test(axl.Defector(), expected_actions=actions, seed=144) diff --git a/docs/tutorials/getting_started/running_axelrods_first_tournament.rst b/docs/tutorials/getting_started/running_axelrods_first_tournament.rst index 7c961591f..7f49e30cd 100644 --- a/docs/tutorials/getting_started/running_axelrods_first_tournament.rst +++ b/docs/tutorials/getting_started/running_axelrods_first_tournament.rst @@ -71,9 +71,10 @@ The results object contains the ranked names:: First by Anonymous Random: 0.5 -We see that `TitForTat` does not in fact win this tournament. `TitForTat` typically does not -win, possibly because our implementations differ from the original strategies as their code -is not available. + +We see that `TitForTat` does not win. In fact `TitForTat` typically does not +win this tournament, possibly because our implementations differ from the original +strategies as their code is not available. We can plot the reported rank (from [Axelrod1980]_) versus the reproduced one:: From b0e00fe53ba7a8172ad50e3123791be6ff7dbb75 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Wed, 5 Aug 2020 08:26:41 -0700 Subject: [PATCH 118/121] Rerun isort --- axelrod/random_.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axelrod/random_.py b/axelrod/random_.py index 1197a097b..b943aae56 100644 --- a/axelrod/random_.py +++ b/axelrod/random_.py @@ -1,9 +1,9 @@ from typing import Optional + import numpy as np from axelrod.action import Action from numpy.random import RandomState - C, D = Action.C, Action.D From 11a6f1c0763b252bac94f52c25e1b3acd934080d Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sat, 8 Aug 2020 15:56:13 -0700 Subject: [PATCH 119/121] A few minor updates following some additional review --- axelrod/evolvable_player.py | 2 +- axelrod/player.py | 3 +-- axelrod/random_.py | 3 +-- axelrod/strategies/finite_state_machines.py | 1 - axelrod/strategies/meta.py | 5 +---- axelrod/strategies/rand.py | 1 + axelrod/tests/unit/test_tournament.py | 9 ++++++--- 7 files changed, 11 insertions(+), 13 deletions(-) diff --git a/axelrod/evolvable_player.py b/axelrod/evolvable_player.py index 57926bac6..7dd4073b3 100644 --- a/axelrod/evolvable_player.py +++ b/axelrod/evolvable_player.py @@ -35,7 +35,7 @@ def overwrite_init_kwargs(self, **kwargs): def create_new(self, **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 in my the mutation and crossover methods.""" + used by the mutation and crossover methods.""" init_kwargs = self.init_kwargs.copy() init_kwargs.update(kwargs) # Propagate seed forward for reproducibility. diff --git a/axelrod/player.py b/axelrod/player.py index 496e9fecf..6b7370be2 100644 --- a/axelrod/player.py +++ b/axelrod/player.py @@ -118,8 +118,7 @@ def _post_init(self): pass def _post_transform(self): - """Overwrite this function if you need to do a post-transform, post-init - modification.""" + """Handles post transform tasks such as further reclassifying.""" # Reclassify strategy post __init__, if needed. for (reclassifier, args, kwargs) in self._reclassifiers: self.classifier = reclassifier(self.classifier, *args, **kwargs) diff --git a/axelrod/random_.py b/axelrod/random_.py index b943aae56..23949c93e 100644 --- a/axelrod/random_.py +++ b/axelrod/random_.py @@ -84,8 +84,7 @@ def random_flip(self, action: Action, threshold: float) -> Action: return action def randrange(self, a: int, b: int) -> int: - """Python 2 / 3 compatible randrange. Returns a random integer uniformly - between a and b (inclusive)""" + """Returns a random integer uniformly between a and b: [a, b).""" c = b - a r = c * self.random() return a + int(r) diff --git a/axelrod/strategies/finite_state_machines.py b/axelrod/strategies/finite_state_machines.py index 2c7178f4f..f9be7b0da 100644 --- a/axelrod/strategies/finite_state_machines.py +++ b/axelrod/strategies/finite_state_machines.py @@ -114,7 +114,6 @@ def __init__( initial_state: int = 1, initial_action: Action = C ) -> None: - # super().__init__() Player.__init__(self) self.initial_state = initial_state self.initial_action = initial_action diff --git a/axelrod/strategies/meta.py b/axelrod/strategies/meta.py index c5ba74a06..5f15b340e 100644 --- a/axelrod/strategies/meta.py +++ b/axelrod/strategies/meta.py @@ -104,7 +104,7 @@ def update_histories(self, coplay): # As a sanity check, look for at least one reclassifier, otherwise # this try-except clause could hide a bug. if len(self._reclassifiers) == 0: - raise TypeError("MetaClass update_histories issue, expected a transformer.") + raise TypeError("MetaClass update_histories issue, expected a reclassifier.") # Otherwise just update with C always, so at least the histories have the # expected length. for player in self.team: @@ -223,9 +223,6 @@ class MetaWinnerEnsemble(MetaWinner): name = "Meta Winner Ensemble" - def __init__(self, team=None): - super().__init__(team=team) - def _post_init(self): super()._post_init() team = list(t.__class__ for t in self.team) diff --git a/axelrod/strategies/rand.py b/axelrod/strategies/rand.py index 499790acf..3987ac344 100644 --- a/axelrod/strategies/rand.py +++ b/axelrod/strategies/rand.py @@ -44,6 +44,7 @@ def strategy(self, opponent: Player) -> Action: return self._random.random_choice(self.p) def _post_init(self): + super()._post_init() if self.p in [0, 1]: self.classifier["stochastic"] = False # Avoid calls to _random, if strategy is deterministic diff --git a/axelrod/tests/unit/test_tournament.py b/axelrod/tests/unit/test_tournament.py index 19f2ebc05..5d5654ff0 100644 --- a/axelrod/tests/unit/test_tournament.py +++ b/axelrod/tests/unit/test_tournament.py @@ -735,14 +735,17 @@ def test_write_to_csv_without_results(self): @settings(max_examples=5, deadline=None) def test_seeding_equality(self, seed): """Tests that a tournament with a given seed will return the - same results each time.""" + same results each time. This specifically checks when running using + multiple cores so as to confirm that + https://github.com/Axelrod-Python/Axelrod/issues/1277 + is fixed.""" rng = axl.RandomGenerator(seed=seed) players = [axl.Random(rng.random()) for _ in range(8)] tournament1 = axl.Tournament( name=self.test_name, players=players, game=self.game, - turns=3, + turns=10, repetitions=100, seed=seed ) @@ -750,7 +753,7 @@ def test_seeding_equality(self, seed): name=self.test_name, players=players, game=self.game, - turns=3, + turns=10, repetitions=100, seed=seed ) From 23a7cf485ae08cdec1db637ad94bcfa09fc99963 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sun, 9 Aug 2020 11:04:47 -0700 Subject: [PATCH 120/121] Update two tests following review comments. --- axelrod/tests/unit/test_match.py | 4 ++-- axelrod/tests/unit/test_tournament.py | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/axelrod/tests/unit/test_match.py b/axelrod/tests/unit/test_match.py index 51aa291de..84040eaa5 100644 --- a/axelrod/tests/unit/test_match.py +++ b/axelrod/tests/unit/test_match.py @@ -359,8 +359,8 @@ def test_sample_length(self): (2, 0.6, 1), (3, 0.4, 2), ]: - rg = RandomGenerator(seed) - r = rg.random() + rng = RandomGenerator(seed) + r = rng.random() self.assertEqual(axl.match.sample_length(prob_end, r), expected_length) def test_sample_with_0_prob(self): diff --git a/axelrod/tests/unit/test_tournament.py b/axelrod/tests/unit/test_tournament.py index 5d5654ff0..f8cca4bb4 100644 --- a/axelrod/tests/unit/test_tournament.py +++ b/axelrod/tests/unit/test_tournament.py @@ -757,9 +757,10 @@ def test_seeding_equality(self, seed): repetitions=100, seed=seed ) - results1 = tournament1.play(processes=2) - results2 = tournament2.play(processes=2) - self.assertEqual(results1.ranked_names, results2.ranked_names) + for _ in range(4): + results1 = tournament1.play(processes=2) + results2 = tournament2.play(processes=2) + self.assertEqual(results1.ranked_names, results2.ranked_names) def test_seeding_inequality(self): players = [axl.Random(0.4), axl.Random(0.6)] From 82a22ca21e2db62a961338479435bc2642078566 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sun, 9 Aug 2020 14:46:06 -0700 Subject: [PATCH 121/121] Various updates following Nik's review --- axelrod/strategies/axelrod_second.py | 4 +-- axelrod/strategies/calculator.py | 2 +- axelrod/strategies/finite_state_machines.py | 6 ++-- axelrod/strategies/gambler.py | 4 +-- axelrod/strategies/memorytwo.py | 8 +++--- axelrod/tests/property.py | 2 +- .../tests/strategies/test_averagecopier.py | 6 ++-- .../tests/strategies/test_axelrod_first.py | 2 +- .../tests/strategies/test_axelrod_second.py | 10 ++++--- axelrod/tests/strategies/test_cycler.py | 1 - .../tests/strategies/test_evolvable_player.py | 4 +-- axelrod/tests/unit/test_fingerprint.py | 28 +++++++++---------- .../tests/unit/test_strategy_transformers.py | 1 - .../approximate_moran_processes.rst | 2 +- 14 files changed, 39 insertions(+), 41 deletions(-) diff --git a/axelrod/strategies/axelrod_second.py b/axelrod/strategies/axelrod_second.py index 4f536f9b5..3ab54f40a 100644 --- a/axelrod/strategies/axelrod_second.py +++ b/axelrod/strategies/axelrod_second.py @@ -433,7 +433,7 @@ class SecondByGrofman(Player): - If its own previous move was D and the opponent has defected more than once in the last 8\* rounds, defect - \* The code looks at the first 7 of the last 8 rounds, ignoring the most + The code looks at the first 7 of the last 8 rounds, ignoring the most recent round. Names: @@ -1530,7 +1530,7 @@ class SecondByWhite(Player): Strategy submitted to Axelrod's second tournament by Edward C White (K72R) and came in thirteenth in that tournament. - * Cooperate in the first ten turns + * Cooperate in the first ten turns. * If the opponent Cooperated last turn then Cooperate. * Otherwise Defect if and only if: floor(log(turn)) * opponent Defections >= turn diff --git a/axelrod/strategies/calculator.py b/axelrod/strategies/calculator.py index 4f2490a74..05cdba01e 100644 --- a/axelrod/strategies/calculator.py +++ b/axelrod/strategies/calculator.py @@ -32,7 +32,7 @@ def __init__(self) -> None: self.joss_instance = Joss() super().__init__() - def set_seed(self, seed=None): + def set_seed(self, seed: int = None): super().set_seed(seed) self.joss_instance.set_seed(seed) diff --git a/axelrod/strategies/finite_state_machines.py b/axelrod/strategies/finite_state_machines.py index f9be7b0da..29fa1a291 100644 --- a/axelrod/strategies/finite_state_machines.py +++ b/axelrod/strategies/finite_state_machines.py @@ -1,5 +1,5 @@ import itertools -from typing import Any, Dict, Sequence, Text, Tuple +from typing import Any, Dict, List, Sequence, Text, Tuple from axelrod.action import Action from axelrod.evolvable_player import ( @@ -200,7 +200,7 @@ def random_params(self, num_states: int) -> Tuple[Tuple[Transition, ...], int, A initial_action = self._random.choice(actions) return tuple(rows), initial_state, initial_action - def mutate_rows(self, rows, mutation_probability): + def mutate_rows(self, rows: List[List], mutation_probability: float): rows = list(rows) randoms = self._random.random(len(rows)) # Flip each value with a probability proportional to the mutation rate @@ -239,7 +239,7 @@ def mutate(self): initial_action=initial_action, ) - def crossover_rows(self, rows1, rows2): + def crossover_rows(self, rows1: List[List], rows2: List[List]) -> List[List]: num_states = len(rows1) // 2 cross_point = 2 * self._random.randint(0, num_states) new_rows = copy_lists(rows1[:cross_point]) diff --git a/axelrod/strategies/gambler.py b/axelrod/strategies/gambler.py index e6f9ccd60..3e50a76b9 100644 --- a/axelrod/strategies/gambler.py +++ b/axelrod/strategies/gambler.py @@ -89,10 +89,10 @@ def __init__( # The mutate and crossover methods are mostly inherited from EvolvableLookerUp, except for the following # modifications. - def random_value(self): + def random_value(self) -> float: return self._random.random() - def mutate_value(self, value): + def mutate_value(self, value: float) -> float: ep = self._random.uniform(-1, 1) / 4 value += ep if value < 0: diff --git a/axelrod/strategies/memorytwo.py b/axelrod/strategies/memorytwo.py index f59db96fe..9573c308f 100644 --- a/axelrod/strategies/memorytwo.py +++ b/axelrod/strategies/memorytwo.py @@ -53,7 +53,7 @@ class MemoryTwoPlayer(Player): } def __init__( - self, sixteen_vector: Tuple[float, ...] = None, initial: Optional[Tuple[Action, Action]] = None + self, sixteen_vector: Optional[Tuple[float, ...]] = None, initial: Optional[Tuple[Action, Action]] = None ) -> None: """ Parameters @@ -69,14 +69,14 @@ def __init__( self._initial = initial self.set_initial_sixteen_vector(sixteen_vector) - def set_initial_sixteen_vector(self, sixteen_vector): + def set_initial_sixteen_vector(self, sixteen_vector: Optional[Tuple[float, ...]]): if sixteen_vector is None: sixteen_vector = tuple([1] * 16) warnings.warn("Memory two player is set to default, Cooperator.") self.set_sixteen_vector(sixteen_vector) - def set_sixteen_vector(self, sixteen_vector: Tuple): + def set_sixteen_vector(self, sixteen_vector: Tuple[float, ...]): if not all(0 <= p <= 1 for p in sixteen_vector): raise ValueError( "An element in the probability vector, {}, is not " @@ -92,7 +92,7 @@ def set_sixteen_vector(self, sixteen_vector: Tuple): ) # type: Dict[tuple, float] @staticmethod - def compute_memory_depth(sixteen_vector): + def compute_memory_depth(sixteen_vector: Dict[Tuple[Action, Action], float]) -> int: values = set(list(sixteen_vector.values())) # Memory-depth 0 diff --git a/axelrod/tests/property.py b/axelrod/tests/property.py index b53edb7c1..c3dc009cc 100644 --- a/axelrod/tests/property.py +++ b/axelrod/tests/property.py @@ -153,7 +153,7 @@ def prob_end_tournaments( min_repetitions : integer The minimum number of repetitions max_repetitions : integer - The maximum number of + The maximum number of repetitions seed : integer Random seed """ diff --git a/axelrod/tests/strategies/test_averagecopier.py b/axelrod/tests/strategies/test_averagecopier.py index 77cbe9ef0..8c162f94a 100644 --- a/axelrod/tests/strategies/test_averagecopier.py +++ b/axelrod/tests/strategies/test_averagecopier.py @@ -40,7 +40,6 @@ def test_defect_if_opponent_always_defects2(self): self.versus_test(axl.Defector(), expected_actions=actions, seed=2) def test_random_behavior1(self): - # Variable behaviour based on the history and stochastic actions = [ (C, C), (C, D), @@ -118,17 +117,16 @@ class TestNiceAverageCopier(TestPlayer): } def test_cooperate_if_opponent_always_cooperates(self): - # Tests that if opponent has played all C then player chooses C. + """Tests that if opponent has played all C then player chooses C.""" actions = [(C, C)] * 10 self.versus_test(axl.Cooperator(), expected_actions=actions, seed=1) def test_defect_if_opponent_always_defects(self): - # Tests that if opponent has played all D then player chooses D. + """Tests that if opponent has played all D then player chooses D.""" actions = [(C, D)] + [(D, D)] * 9 self.versus_test(axl.Defector(), expected_actions=actions, seed=1) def test_random_behavior1(self): - # Variable behaviour based on the history and stochastic behaviour actions = [ (C, C), (C, D), diff --git a/axelrod/tests/strategies/test_axelrod_first.py b/axelrod/tests/strategies/test_axelrod_first.py index 02825fd88..40f09e80c 100644 --- a/axelrod/tests/strategies/test_axelrod_first.py +++ b/axelrod/tests/strategies/test_axelrod_first.py @@ -38,7 +38,7 @@ def test_cooperate_first_ten_rounds3(self): def test_retaliation_after_ten_rounds1(self): """If opponent defects at any point then the player will defect forever - (after 10 rounds)""" + (after 10 rounds).""" opponent = axl.MockPlayer(actions=[C] * 10 + [D]) actions = [(C, C)] * 10 + [(C, D), (D, C)] self.versus_test(opponent, expected_actions=actions) diff --git a/axelrod/tests/strategies/test_axelrod_second.py b/axelrod/tests/strategies/test_axelrod_second.py index 38ec895e2..26fa8078e 100644 --- a/axelrod/tests/strategies/test_axelrod_second.py +++ b/axelrod/tests/strategies/test_axelrod_second.py @@ -22,15 +22,17 @@ class TestChampion(TestPlayer): def test_initial_rounds(self): # Cooperates for first 10 rounds - actions = [(C, D)] * 10 # Cooperate for ten rounds + actions = [(C, D)] * 10 self.versus_test(axl.Defector(), expected_actions=actions) # Mirror partner for next phase - actions += [(D, D)] * 7 # Mirror opponent afterwards + actions += [(D, D)] * 7 self.versus_test(axl.Defector(), expected_actions=actions) def test_cooperate_until_defect(self): - actions = [(C, C), (C, D)] * 5 # Cooperate for ten rounds - actions += [(D, C), (C, D)] * 7 # Mirror opponent afterwards + # Cooperates for first 10 rounds + actions = [(C, C), (C, D)] * 5 + # Mirror partner for next phase + actions += [(D, C), (C, D)] * 7 # Cooperate unless the opponent defected, has defected at least 40% of actions += [(D, C), (C, D), (C, C), (C, D)] * 3 self.versus_test(axl.Alternator(), expected_actions=actions, seed=2) diff --git a/axelrod/tests/strategies/test_cycler.py b/axelrod/tests/strategies/test_cycler.py index 436a464a5..675d4b4d5 100644 --- a/axelrod/tests/strategies/test_cycler.py +++ b/axelrod/tests/strategies/test_cycler.py @@ -30,7 +30,6 @@ class TestAntiCycler(TestPlayer): } def test_has_no_cycles(self): - # test_range = 100 player = axl.AntiCycler() opponent = axl.Cooperator() match = axl.Match((player, opponent), turns=100) diff --git a/axelrod/tests/strategies/test_evolvable_player.py b/axelrod/tests/strategies/test_evolvable_player.py index caac37d10..6ed840b0b 100644 --- a/axelrod/tests/strategies/test_evolvable_player.py +++ b/axelrod/tests/strategies/test_evolvable_player.py @@ -98,8 +98,8 @@ def test_mutate_variations(self): if not self.init_parameters: return variants_produced = False - for seed in range(2, 400): - player = self.player(seed=seed) + for seed_ in range(2, 400): + player = self.player(seed=seed_) mutant = player.mutate() if player != mutant: variants_produced = True diff --git a/axelrod/tests/unit/test_fingerprint.py b/axelrod/tests/unit/test_fingerprint.py index c9ad25b19..9c727f91e 100644 --- a/axelrod/tests/unit/test_fingerprint.py +++ b/axelrod/tests/unit/test_fingerprint.py @@ -381,20 +381,20 @@ def test_pair_fingerprints(self, strategy_pair): A test to check that we can fingerprint with any two given strategies or instances """ - # strategy, probe = strategy_pair - # af = AshlockFingerprint(strategy, probe) - # data = af.fingerprint(turns=2, repetitions=2, step=0.5, progress_bar=False, seed=1) - # self.assertIsInstance(data, dict) - # - # strategy, probe = strategy_pair - # af = AshlockFingerprint(strategy(), probe) - # data = af.fingerprint(turns=2, repetitions=2, step=0.5, progress_bar=False, seed=2) - # self.assertIsInstance(data, dict) - # - # strategy, probe = strategy_pair - # af = AshlockFingerprint(strategy, probe()) - # data = af.fingerprint(turns=2, repetitions=2, step=0.5, progress_bar=False, seed=3) - # self.assertIsInstance(data, dict) + strategy, probe = strategy_pair + af = AshlockFingerprint(strategy, probe) + data = af.fingerprint(turns=2, repetitions=2, step=0.5, progress_bar=False, seed=1) + self.assertIsInstance(data, dict) + + strategy, probe = strategy_pair + af = AshlockFingerprint(strategy(), probe) + data = af.fingerprint(turns=2, repetitions=2, step=0.5, progress_bar=False, seed=2) + self.assertIsInstance(data, dict) + + strategy, probe = strategy_pair + af = AshlockFingerprint(strategy, probe()) + data = af.fingerprint(turns=2, repetitions=2, step=0.5, progress_bar=False, seed=3) + self.assertIsInstance(data, dict) strategy, probe = strategy_pair af = AshlockFingerprint(strategy(), probe()) diff --git a/axelrod/tests/unit/test_strategy_transformers.py b/axelrod/tests/unit/test_strategy_transformers.py index adaa05d1c..7539ae8cd 100644 --- a/axelrod/tests/unit/test_strategy_transformers.py +++ b/axelrod/tests/unit/test_strategy_transformers.py @@ -434,7 +434,6 @@ def test_implementation(self): """A test that demonstrates the difference in outcomes if FlipTransformer is applied to Alternator and CyclerCD. In other words, the implementation matters, not just the outcomes.""" - # Difference between Alternator and CyclerCD p1 = axl.Cycler(cycle="CD") p2 = FlipTransformer()(axl.Cycler)(cycle="CD") self.versus_test(p1, p2, [C, D, C, D, C], [D, C, D, C, D]) diff --git a/docs/tutorials/further_topics/approximate_moran_processes.rst b/docs/tutorials/further_topics/approximate_moran_processes.rst index e8d984007..8bcc4eb5d 100644 --- a/docs/tutorials/further_topics/approximate_moran_processes.rst +++ b/docs/tutorials/further_topics/approximate_moran_processes.rst @@ -31,7 +31,7 @@ even for the same random seed. We see that, for this random seed, the :code:`Ran won this Moran process. This is not what happens in a standard Moran process where the :code:`Random: 0.5` player will not win:: - >>> mp = axl.MoranProcess(players, seed=2) + >>> mp = axl.MoranProcess(players, seed=5) >>> results = mp.play() >>> mp.population_distribution() Counter({'Defector': 3})