diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 4599f2d8f..62f0c4712 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: | @@ -51,8 +49,8 @@ jobs: python run_mypy.py - name: Check imports are sorted run: | - python -m pip install isort - python -m isort --check-only + python -m pip install "isort==4.3.21" + python -m isort --check-only --recursive axelrod/. - name: Check that all strategies are indexed run: | python run_strategy_indexer.py 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/__init__.py b/axelrod/__init__.py index 90eb959e1..573af1bb9 100644 --- a/axelrod/__init__.py +++ b/axelrod/__init__.py @@ -3,10 +3,18 @@ # The order of imports matters! from axelrod.version import __version__ +from axelrod.action import Action +from axelrod.random_ import Pdf, RandomGenerator, BulkRandomGenerator + +# Initialize module level Random +# This is initially seeded by the clock / OS entropy pool +# It is not used if user specifies seeds everywhere and should only be +# used internally by the library and in certain tests that need to set +# its seed. +_module_random = RandomGenerator() + from axelrod.load_data_ import load_pso_tables, load_weights from axelrod import graph -from axelrod.action import Action -from axelrod.random_ import random_choice, random_flip, seed, Pdf from axelrod.plot import Plot from axelrod.game import DefaultGame, Game from axelrod.history import History, LimitedHistory 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/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/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/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/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: diff --git a/axelrod/evolvable_player.py b/axelrod/evolvable_player.py index c0411780e..7dd4073b3 100644 --- a/axelrod/evolvable_player.py +++ b/axelrod/evolvable_player.py @@ -1,6 +1,5 @@ import base64 from pickle import dumps, loads -from random import randrange from typing import Dict, List from .player import Player @@ -22,15 +21,26 @@ class EvolvablePlayer(Player): parent_class = Player parent_kwargs = [] # type: List[str] + def __init__(self, seed=None): + # Parameter seed is required for reproducibility. Player will throw + # a warning to the user otherwise. + super().__init__() + self.set_seed(seed=seed) + def overwrite_init_kwargs(self, **kwargs): """Use to overwrite parameters for proper cloning and testing.""" for k, v in kwargs.items(): self.init_kwargs[k] = v def create_new(self, **kwargs): - """Creates a new variant with parameters overwritten by kwargs.""" + """Creates a new variant with parameters overwritten by kwargs. This differs from + cloning the Player because it propagates a seed forward, and is intended to be + used by the mutation and crossover methods.""" init_kwargs = self.init_kwargs.copy() init_kwargs.update(kwargs) + # Propagate seed forward for reproducibility. + if "seed" not in kwargs: + init_kwargs["seed"] = self._random.random_seed_int() return self.__class__(**init_kwargs) # Serialization and deserialization. You may overwrite to obtain more human readable serializations @@ -74,15 +84,15 @@ def copy_lists(lists: List[List]) -> List[List]: return list(map(list, lists)) -def crossover_lists(list1: List, list2: List) -> List: - cross_point = randrange(len(list1)) +def crossover_lists(list1: List, list2: List, rng) -> List: + cross_point = rng.randint(0, len(list1)) new_list = list(list1[:cross_point]) + list(list2[cross_point:]) return new_list -def crossover_dictionaries(table1: Dict, table2: Dict) -> Dict: +def crossover_dictionaries(table1: Dict, table2: Dict, rng) -> Dict: keys = list(table1.keys()) - cross_point = randrange(len(keys)) + cross_point = rng.randint(0, len(keys)) new_items = [(k, table1[k]) for k in keys[:cross_point]] new_items += [(k, table2[k]) for k in keys[cross_point:]] new_table = dict(new_items) diff --git a/axelrod/fingerprint.py b/axelrod/fingerprint.py index 413926b6e..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 @@ -244,7 +244,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 @@ -264,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 @@ -278,6 +277,7 @@ def fingerprint( processes: int = None, filename: str = None, progress_bar: bool = True, + seed: int = None ) -> dict: """Build and play the spatial tournament. @@ -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 ---------- @@ -321,7 +323,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, @@ -429,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. @@ -475,6 +479,7 @@ def fingerprint( turns=turns, noise=noise, repetitions=repetitions, + seed=seed ) tournament.play( filename=filename, 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.""" diff --git a/axelrod/makes_use_of.py b/axelrod/makes_use_of.py index 41356123c..cd6bc16d9 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 @@ -25,17 +29,20 @@ 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) 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/match.py b/axelrod/match.py index 92d42b4c9..907e85f44 100644 --- a/axelrod/match.py +++ b/axelrod/match.py @@ -1,12 +1,11 @@ -import random from math import ceil, log import axelrod.interaction_utils as iu from axelrod import DEFAULT_TURNS, Classifiers from axelrod.action import Action +from axelrod.deterministic_cache import DeterministicCache from axelrod.game import Game - -from .deterministic_cache import DeterministicCache +from axelrod.random_ import RandomGenerator C, D = Action.C, Action.D @@ -30,6 +29,7 @@ def __init__( noise=0, match_attributes=None, reset=True, + seed=None ): """ Parameters @@ -52,6 +52,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 +67,8 @@ def __init__( self.result = [] self.noise = noise + self.set_seed(seed) + if game is None: self.game = Game() else: @@ -88,6 +92,17 @@ def __init__( self.players = list(players) 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) + @property def players(self): return self._players @@ -129,6 +144,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.random_flip(s1, noise) + s2 = self._random.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 +175,11 @@ def play(self): i.e. One entry per turn containing a pair of actions. """ - turns = min(sample_length(self.prob_end), 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): @@ -155,9 +187,13 @@ def play(self): if self.reset: p.reset() p.set_match_attributes(**self.match_attributes) + # 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.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 +252,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 +285,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..c0ad188bd 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 @@ -34,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 @@ -43,6 +48,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 +79,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 ca011e6ed..3c9876c40 100644 --- a/axelrod/moran.py +++ b/axelrod/moran.py @@ -1,45 +1,15 @@ """Implementation of the Moran process on Graphs.""" -import random from collections import Counter from typing import Callable, List, Optional, Set, Tuple import matplotlib.pyplot as plt import numpy as np from axelrod import DEFAULT_TURNS, EvolvablePlayer, Game, Player - -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 axelrod.deterministic_cache import DeterministicCache +from axelrod.graph import Graph, complete_graph +from axelrod.match import Match +from axelrod.random_ import BulkRandomGenerator, RandomGenerator class MoranProcess(object): @@ -57,7 +27,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 @@ -115,19 +86,9 @@ 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 - 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 m = mutation_method.lower() if m in ["atomic", "transition"]: self.mutation_method = m @@ -142,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]) @@ -175,13 +150,45 @@ 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, + 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 +206,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 +230,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] @@ -250,13 +257,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 @@ -363,6 +370,7 @@ def score_all(self) -> List: noise=self.noise, game=self.game, deterministic_cache=self.deterministic_cache, + seed=next(self._bulk_random) ) match.play() match_scores = match.final_score_per_turn() @@ -476,7 +484,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,9 +503,18 @@ def __init__( noise=0, deterministic_cache=None, mutation_rate=mutation_rate, + seed=seed ) 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 @@ -512,7 +530,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/player.py b/axelrod/player.py index 32ef5291c..6b7370be2 100644 --- a/axelrod/player.py +++ b/axelrod/player.py @@ -2,29 +2,77 @@ import inspect import itertools import types +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 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 PostInitCaller(type): + """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 -class Player(object): + 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 + + +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. @@ -32,6 +80,7 @@ class Player(object): name = "Player" classifier = {} # type: Dict[str, Any] + _reclassifiers = [] def __new__(cls, *args, **kwargs): """Caches arguments for Player cloning.""" @@ -44,7 +93,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__) @@ -59,14 +108,24 @@ 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): + """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) + 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 @@ -76,6 +135,10 @@ def __eq__(self, other): value = getattr(self, attribute, None) other_value = getattr(other, attribute, None) + if attribute in ["_random", "_seed"]: + # Don't compare the random generators. + continue + if isinstance(value, np.ndarray): if not (np.array_equal(value, other_value)): return False @@ -122,6 +185,17 @@ 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): + """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() + else: + self._seed = seed + self._random = RandomGenerator(seed=self._seed) + def __repr__(self): """The string method for the strategy. Appends the `__init__` parameters to the strategy's name.""" @@ -146,10 +220,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.""" diff --git a/axelrod/random_.py b/axelrod/random_.py index 95d7700d9..23949c93e 100644 --- a/axelrod/random_.py +++ b/axelrod/random_.py @@ -1,94 +1,146 @@ -import random +from typing import Optional import numpy as np from axelrod.action import Action -from numpy.random import choice +from numpy.random import RandomState 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: 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_: Optional[int] = None): + """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 random_seed_int(self) -> int: + return self.randint(low=0, high=2**32-1, dtype="uint64") - Returns - ------- - axelrod.Action - """ - if p == 0: - return D + def choice(self, *args, **kwargs): + return self._random.choice(*args, **kwargs) + + 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: + """Returns a random integer uniformly between a and b: [a, b).""" + 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. Bulk generation of random values is more efficient. + Use this class like a generator.""" + def __init__(self, seed=None, batch_size:int = 1000): + self._random_generator = RandomState() + self._random_generator.seed(seed) + self._ints = None + self._batch_size = batch_size + self._index = 0 + self._fill_ints() + + def _fill_ints(self): + # 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, + dtype="uint64") + self._index = 0 + + def __next__(self): + try: + x = self._ints[self._index] + except IndexError: + self._fill_ints() + x = self._ints[self._index] + self._index += 1 + return x diff --git a/axelrod/strategies/adaptor.py b/axelrod/strategies/adaptor.py index fc831d575..f8c550f61 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 C, D = Action.C, Action.D @@ -54,7 +53,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/ann.py b/axelrod/strategies/ann.py index ce5ad49dc..74bd1ab7e 100644 --- a/axelrod/strategies/ann.py +++ b/axelrod/strategies/ann.py @@ -1,7 +1,6 @@ from typing import List, Tuple import numpy as np -import numpy.random as random from axelrod.action import Action from axelrod.evolvable_player import ( EvolvablePlayer, @@ -197,7 +196,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) @@ -232,14 +231,15 @@ def __init__( weights: List[float] = None, mutation_probability: float = None, mutation_distance: int = 5, + seed: int = None ) -> None: + 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( @@ -248,25 +248,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 @@ -279,7 +277,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/averagecopier.py b/axelrod/strategies/averagecopier.py index 7ee36ddbd..131a9cca0 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 @@ -28,9 +27,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): @@ -56,4 +55,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 8f9765f73..0cc16d4e6 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, Optional, Tuple 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 @@ -352,7 +350,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): @@ -446,11 +444,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 @@ -480,10 +478,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): @@ -514,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) @@ -738,15 +736,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: @@ -755,7 +746,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): @@ -792,10 +783,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 4029d552a..3ab54f40a 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 @@ -55,11 +53,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. @@ -84,8 +83,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 @@ -95,7 +93,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): @@ -398,7 +396,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 @@ -409,7 +407,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] @@ -435,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: @@ -558,7 +556,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 @@ -743,8 +741,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 defection (if opponent has defected at least 18 times) or with a random (50/50) choice. [Cooperate on first.] Names: @@ -782,7 +780,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 @@ -822,7 +820,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 @@ -1317,7 +1315,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) @@ -1471,7 +1469,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): @@ -1522,7 +1520,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])] ) @@ -1532,8 +1530,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 @@ -1604,7 +1602,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): @@ -2104,4 +2102,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 cca647a9c..5a231187f 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 @@ -28,4 +27,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 9c59929a9..3fed4127b 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 @@ -123,9 +120,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 5353a4b2e..05cdba01e 100644 --- a/axelrod/strategies/calculator.py +++ b/axelrod/strategies/calculator.py @@ -29,8 +29,12 @@ class Calculator(Player): } def __init__(self) -> None: - super().__init__() self.joss_instance = Joss() + super().__init__() + + def set_seed(self, seed: int = 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/cycler.py b/axelrod/strategies/cycler.py index 7e2d9cacf..bff8c83d4 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 @@ -87,7 +86,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) @@ -111,12 +110,12 @@ 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: + 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, @@ -124,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: @@ -162,10 +159,10 @@ 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) + 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 07a8bdae6..974c6f575 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/strategies/finite_state_machines.py b/axelrod/strategies/finite_state_machines.py index 36c28b88a..29fa1a291 100644 --- a/axelrod/strategies/finite_state_machines.py +++ b/axelrod/strategies/finite_state_machines.py @@ -1,8 +1,6 @@ import itertools -from random import randrange -from typing import Any, Dict, List, Sequence, Text, Tuple, Union +from typing import Any, Dict, List, Sequence, Text, Tuple -import numpy.random as random from axelrod.action import Action from axelrod.evolvable_player import ( EvolvablePlayer, @@ -10,7 +8,6 @@ copy_lists, ) from axelrod.player import Player -from numpy.random import choice C, D = Action.C, Action.D actions = (C, D) @@ -117,7 +114,7 @@ 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 self.fsm = SimpleFSM(transitions, initial_state) @@ -150,9 +147,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.""" + EvolvablePlayer.__init__(self, seed=seed) transitions, initial_state, initial_action, num_states = self._normalize_parameters( transitions, initial_state, initial_action, num_states) FSMPlayer.__init__( @@ -160,7 +159,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, @@ -176,14 +174,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 @@ -191,32 +188,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: List[List], mutation_probability: float): 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 @@ -227,11 +222,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) @@ -244,10 +239,9 @@ def mutate(self): initial_action=initial_action, ) - @staticmethod - def crossover_rows(rows1, rows2): + def crossover_rows(self, rows1: List[List], rows2: List[List]) -> List[List]: 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 647b78f15..3e50a76b9 100644 --- a/axelrod/strategies/gambler.py +++ b/axelrod/strategies/gambler.py @@ -4,13 +4,11 @@ For the original see: https://gist.github.com/GDKO/60c3d0fd423598f3c4e4 """ -import random from typing import Any from axelrod.action import Action, actions_to_str, str_to_actions from axelrod.load_data_ import load_pso_tables from axelrod.player import Player -from axelrod.random_ import random_choice from .lookerup import ( EvolvableLookerUp, @@ -48,7 +46,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): @@ -60,7 +58,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, @@ -68,7 +67,8 @@ def __init__( initial_actions=initial_actions, pattern=pattern, parameters=parameters, - mutation_probability=mutation_probability + mutation_probability=mutation_probability, + seed=seed ) self.pattern = list(self.pattern) Gambler.__init__( @@ -89,13 +89,11 @@ def __init__( # The mutate and crossover methods are mostly inherited from EvolvableLookerUp, except for the following # modifications. - @classmethod - def random_value(cls): - return random.random() + def random_value(self) -> float: + return self._random.random() - @classmethod - def mutate_value(cls, value): - ep = random.uniform(-1, 1) / 4 + def mutate_value(self, value: float) -> float: + ep = self._random.uniform(-1, 1) / 4 value += ep if value < 0: value = 0 diff --git a/axelrod/strategies/geller.py b/axelrod/strategies/geller.py index d86ed91c7..92e88117f 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 @@ -48,10 +47,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 559b2db26..ad64ddb55 100644 --- a/axelrod/strategies/hmm.py +++ b/axelrod/strategies/hmm.py @@ -1,6 +1,5 @@ -from random import randrange +from typing import Any, Dict -import numpy.random as random from axelrod.action import Action from axelrod.evolvable_player import ( EvolvablePlayer, @@ -9,8 +8,6 @@ crossover_lists, ) from axelrod.player import Player -from axelrod.random_ import random_choice, random_vector -from numpy.random import choice C, D = Action.C, Action.D @@ -33,17 +30,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 @@ -76,6 +73,22 @@ def __init__( self.transitions_D = transitions_D self.emission_probabilities = emission_probabilities self.state = initial_state + 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.""" + # 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: """ @@ -114,14 +127,28 @@ 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: - next_state = 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: - next_state = 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] - action = random_choice(p) + if p == 0: + return D + if p == 1: + return C + action = self._random.random_choice(p) return action @@ -153,7 +180,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]] @@ -190,6 +217,16 @@ 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 + # 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: + pass + class EvolvableHMMPlayer(HMMPlayer, EvolvablePlayer): """Evolvable version of HMMPlayer.""" @@ -203,8 +240,10 @@ def __init__( initial_state=0, initial_action=C, num_states=None, - mutation_probability=None + mutation_probability=None, + seed: int = None ) -> None: + 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 @@ -214,7 +253,7 @@ def __init__( emission_probabilities=emission_probabilities, initial_state=initial_state, initial_action=initial_action) - EvolvablePlayer.__init__(self) + self.hmm._random = self._random self.overwrite_init_kwargs( transitions_C=transitions_C, transitions_D=transitions_D, @@ -225,13 +264,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]: @@ -245,16 +283,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_vector(num_states)) - transitions_D.append(random_vector(num_states)) - emission_probabilities.append(random.random()) - initial_state = 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 @@ -262,10 +299,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 @@ -275,13 +311,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 = 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, @@ -293,10 +329,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/inverse.py b/axelrod/strategies/inverse.py index 02f265c0e..036849288 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 @@ -24,8 +23,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. @@ -44,4 +42,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/lookerup.py b/axelrod/strategies/lookerup.py index 5b2436367..1f57dc685 100644 --- a/axelrod/strategies/lookerup.py +++ b/axelrod/strategies/lookerup.py @@ -2,7 +2,6 @@ from itertools import product from typing import Any, TypeVar -import numpy.random as random from axelrod.action import Action, actions_to_str, str_to_actions from axelrod.evolvable_player import ( EvolvablePlayer, @@ -10,7 +9,6 @@ crossover_dictionaries, ) from axelrod.player import Player -from numpy.random import choice C, D = Action.C, Action.D actions = (C, D) @@ -323,7 +321,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) @@ -405,8 +403,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: + EvolvablePlayer.__init__(self, seed=seed) lookup_dict, initial_actions, pattern, parameters, mutation_probability = self._normalize_parameters( lookup_dict, initial_actions, pattern, parameters, mutation_probability ) @@ -417,7 +417,6 @@ def __init__( pattern=pattern, parameters=parameters, ) - EvolvablePlayer.__init__(self) self.mutation_probability = mutation_probability self.overwrite_init_kwargs( lookup_dict=lookup_dict, @@ -427,29 +426,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 @@ -462,15 +460,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) @@ -478,13 +474,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): @@ -492,7 +487,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( @@ -503,7 +498,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/strategies/memoryone.py b/axelrod/strategies/memoryone.py index bea84bf16..a8ad1c571 100644 --- a/axelrod/strategies/memoryone.py +++ b/axelrod/strategies/memoryone.py @@ -6,11 +6,42 @@ from axelrod.action import Action from axelrod.player import Player -from axelrod.random_ import random_choice 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 +71,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 +82,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 +109,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: @@ -89,34 +124,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 random_choice(p) - - -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, - "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 + try: + return self._random.random_choice(p) + except AttributeError: + return D if p == 0 else C class WinShiftLoseStay(MemoryOnePlayer): @@ -314,7 +325,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 b3965e576..9573c308f 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 @@ -35,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,31 +53,30 @@ class MemoryTwoPlayer(Player): } def __init__( - self, sixteen_vector: Tuple[float, ...] = None, initial: Optional[Action] = None + self, sixteen_vector: Optional[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) - 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,17 +90,50 @@ 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: Dict[Tuple[Action, Action], float]) -> int: + 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:])) ] # Draw a random number in [0, 1] to decide - return random_choice(p) + try: + return self._random.random_choice(p) + except AttributeError: + return C if p == 1 else D class AON2(MemoryTwoPlayer): diff --git a/axelrod/strategies/meta.py b/axelrod/strategies/meta.py index 2c4631614..5f15b340e 100644 --- a/axelrod/strategies/meta.py +++ b/axelrod/strategies/meta.py @@ -1,12 +1,9 @@ -import random - import numpy as np from axelrod.action import Action from axelrod.classifier import Classifiers from axelrod.player import Player from axelrod.strategies import TitForTat from axelrod.strategy_transformers import NiceTransformer -from numpy.random import choice from ._strategies import all_strategies from .hunter import ( @@ -53,16 +50,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", @@ -78,7 +75,11 @@ def __init__(self, team=None): if new_uses: self.classifier["makes_use_of"].update(new_uses) - self._last_results = None + def set_seed(self, seed=None): + super().set_seed(seed=seed) + # Seed the team as well + for t in self.team: + t.set_seed(self._random.random_seed_int()) def receive_match_attributes(self): for t in self.team: @@ -92,8 +93,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 reclassifier.") + # 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) @@ -183,7 +198,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 @@ -199,7 +214,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: @@ -208,13 +223,32 @@ class MetaWinnerEnsemble(MetaWinner): name = "Meta Winner Ensemble" + 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: + 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"](self.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 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 = 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] @@ -506,12 +540,46 @@ class MetaMixer(MetaPlayer): } def __init__(self, team=None, distribution=None): - self.distribution = distribution 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): + distribution = self.distribution + 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. + if distribution: + total = sum(distribution) + 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""" - return choice(results, p=self.distribution) + """Using the _random.choice function to sample with weights.""" + return self._random.choice(results, p=self.distribution) class NMWEDeterministic(NiceMetaWinnerEnsemble): @@ -647,10 +715,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 @@ -659,6 +723,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) @@ -674,14 +744,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: @@ -691,9 +761,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 1cd681195..4e31565df 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 @@ -24,7 +23,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 @@ -49,7 +48,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 @@ -74,7 +73,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 ff8cb778f..c48f56737 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 @@ -28,6 +27,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/oncebitten.py b/axelrod/strategies/oncebitten.py index 977cfb928..db58a27bb 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 @@ -115,7 +113,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/prober.py b/axelrod/strategies/prober.py index 7b4b23fc0..0f5293c61 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] @@ -341,7 +339,11 @@ 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) + if self.p == 0: + return C + if self.p == 1: + return D + choice = self._random.random_choice(1 - self.p) return choice @@ -388,7 +390,10 @@ def strategy(self, opponent: Player) -> Action: return D # Otherwise cooperate with probability 1 - self.p - if 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 diff --git a/axelrod/strategies/punisher.py b/axelrod/strategies/punisher.py index 043dc0012..262901675 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 @@ -135,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/strategies/qlearner.py b/axelrod/strategies/qlearner.py index f1b1c5bce..ef2819e01 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] @@ -42,7 +40,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 @@ -60,7 +58,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) @@ -77,11 +75,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: """ @@ -107,7 +105,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 1d8dcce43..3987ac344 100644 --- a/axelrod/strategies/rand.py +++ b/axelrod/strategies/rand.py @@ -1,6 +1,7 @@ from axelrod.action import Action from axelrod.player import Player -from axelrod.random_ import random_choice + +C, D = Action.C, Action.D class Random(Player): @@ -38,8 +39,25 @@ def __init__(self, p: float = 0.5) -> None: """ super().__init__() self.p = p - if p in [0, 1]: - self.classifier["stochastic"] = False def strategy(self, opponent: Player) -> Action: - return random_choice(self.p) + 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 + # by overwriting the strategy function. + if self.p <= 0: + self.strategy = self.defect + if self.p >= 1: + self.strategy = self.cooperate + + @classmethod + def cooperate(cls, opponent: Player) -> Action: + return C + + @classmethod + def defect(cls, opponent: Player) -> Action: + return D diff --git a/axelrod/strategies/revised_downing.py b/axelrod/strategies/revised_downing.py index 0f78a1a58..a9ea057cd 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/selfsteem.py b/axelrod/strategies/selfsteem.py index 2ef55ce0a..94131659f 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 @@ -47,6 +46,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 cbba3709b..2eced3e89 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 @@ -74,4 +73,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 bd2386a14..31c20b639 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 @@ -122,15 +121,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 @@ -836,7 +834,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: @@ -889,7 +887,10 @@ def strategy(self, opponent: Player) -> Action: if self.act_random: self.act_random = False - return 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/strategies/worse_and_worse.py b/axelrod/strategies/worse_and_worse.py index f428c545f..efa068a79 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 @@ -30,7 +29,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): @@ -56,7 +55,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): @@ -88,7 +87,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): @@ -119,4 +118,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 a870797ea..a7297382d 100644 --- a/axelrod/strategy_transformers.py +++ b/axelrod/strategy_transformers.py @@ -5,20 +5,15 @@ See the various Meta strategies for another type of transformation. """ -import copy import inspect -import random -from collections import Iterable from importlib import import_module from typing import Any from axelrod.strategies.sequence_player import SequencePlayer -from numpy.random import choice from .action import Action -from .makes_use_of import * +from .makes_use_of import makes_use_of_variant from .player import Player -from .random_ import random_choice C, D = Action.C, Action.D @@ -28,6 +23,21 @@ # Alternator. +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_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: + original_classifier["makes_use_of"] = classifier_makes_use_of + return original_classifier + + def StrategyTransformerFactory( strategy_wrapper, name_prefix=None, reclassifier=None ): @@ -90,30 +100,52 @@ def __call__(self, PlayerClass): del kwargs["name_prefix"] except KeyError: pass - try: - del kwargs["reclassifier"] - except KeyError: - pass - - # 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. - self._history = self.history.flip_plays() - if is_strategy_static(PlayerClass): - proposed_action = PlayerClass.strategy(opponent) - else: - proposed_action = PlayerClass.strategy(self, opponent) + # 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. - if strategy_wrapper == dual_wrapper: - # After dual_wrapper calls the strategy, it returns - # the Player to its original state. + 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. + 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() + 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 ) @@ -128,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): @@ -195,9 +217,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, }, ) @@ -245,7 +267,6 @@ def __call__( kwargs: dict, instance_name_prefix: str, ) -> Any: - decorator_class = StrategyTransformerFactory(*factory_args) kwargs["name_prefix"] = instance_name_prefix return decorator_class(*args, **kwargs) @@ -307,7 +328,6 @@ def generic_strategy_wrapper( Returns ------- action: an axelrod.Action, C or D - """ # This example just passes through the proposed_action @@ -360,7 +380,11 @@ 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() + if noise == 0: + return action + if noise == 1: + return action.flip() + r = player._random.random() if r < noise: return action.flip() return action @@ -382,7 +406,11 @@ 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) + if p == 0: + return D + if p == 1: + return C + return player._random.random_choice(p) return C @@ -460,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 @@ -556,16 +588,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 random.random() < mutate_prob: - p = 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 @@ -626,7 +661,14 @@ 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) + + # Avoid use of self._random if strategy is actually deterministic. + # 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 + + action = player._random.choice(options, p=probability) return action @@ -639,8 +681,19 @@ def jossann_reclassifier(original_classifier, probability): probability = tuple([i / sum(probability) for i in probability]) if probability in [(1, 0), (0, 1)]: - original_classifier["stochastic"] = False - elif sum(probability) != 0: + # In this case the player's strategy is never actually called, + # so even if it were stochastic the play is not. + # 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 return original_classifier @@ -671,8 +724,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 ) @@ -682,18 +743,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/integration/test_filtering.py b/axelrod/tests/integration/test_filtering.py index cddffd3da..17a57243c 100644 --- a/axelrod/tests/integration/test_filtering.py +++ b/axelrod/tests/integration/test_filtering.py @@ -20,6 +20,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): @@ -49,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 ): @@ -91,21 +92,15 @@ 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) - def test_makes_use_of_filtering(self, seed_, strategies): + @given(strategies=strategy_lists(min_size=20, max_size=20)) + @settings(max_examples=5, deadline=None) + 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.seed(seed_) comprehension = set( [ s @@ -114,7 +109,6 @@ def test_makes_use_of_filtering(self, seed_, strategies): ] ) - axl.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 f8fa78438..d46ded024 100644 --- a/axelrod/tests/integration/test_matches.py +++ b/axelrod/tests/integration/test_matches.py @@ -39,15 +39,14 @@ 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""" results = [] for _ in range(3): - axl.seed(seed) players = [s() for s in strategies] - results.append(axl.Match(players, turns).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 2b58c4db1..d2d26cf56 100644 --- a/axelrod/tests/integration/test_tournament.py +++ b/axelrod/tests/integration/test_tournament.py @@ -105,7 +105,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 @@ -117,6 +116,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)) @@ -162,8 +162,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 = 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/property.py b/axelrod/tests/property.py index 85991cff9..c3dc009cc 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 @@ -123,12 +129,15 @@ def prob_end_tournaments( max_noise=1, min_repetitions=1, max_repetitions=20, + seed=None ): """ A hypothesis decorator to return a tournament, 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 @@ -145,6 +154,8 @@ def prob_end_tournaments( The minimum number of repetitions max_repetitions : integer The maximum number of repetitions + seed : integer + Random seed """ strategies = draw( strategy_lists(strategies=strategies, min_size=min_size, max_size=max_size) @@ -155,7 +166,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 @@ -178,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 @@ -207,7 +220,6 @@ def spatial_tournaments( lists( sampled_from(all_potential_edges), unique=True, - average_size=2 * len(players), ) ) @@ -246,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 @@ -275,7 +289,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_adaptive.py b/axelrod/tests/strategies/test_adaptive.py index 1fbca26f4..20befc067 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 @@ -21,26 +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() - player.play(opponent) - player.play(opponent) - self.assertEqual(3, player.scores[C]) - game = axl.Game(-3, 10, 10, 10) - player.set_match_attributes(game=game) - player.play(opponent) - 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) diff --git a/axelrod/tests/strategies/test_adaptor.py b/axelrod/tests/strategies/test_adaptor.py index 740fdb252..ddaa28a06 100644 --- a/axelrod/tests/strategies/test_adaptor.py +++ b/axelrod/tests/strategies/test_adaptor.py @@ -1,10 +1,8 @@ """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 @@ -22,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=22 + 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=925 + 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 ) @@ -67,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=22 + 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 ) diff --git a/axelrod/tests/strategies/test_alternator.py b/axelrod/tests/strategies/test_alternator.py index 64a347c33..b4e4054cc 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.CyclerDC() actions = [(C, D), (D, C)] * 5 self.versus_test(opponent, expected_actions=actions) diff --git a/axelrod/tests/strategies/test_ann.py b/axelrod/tests/strategies/test_ann.py index 8ca6b0b5b..985462e34 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) @@ -97,15 +101,16 @@ 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, seed=1)._normalize_parameters, + # To prevent exception from unset seed. ) self.assertRaises( InsufficientParametersError, - self.player_class._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_apavlov.py b/axelrod/tests/strategies/test_apavlov.py index e720dce56..8d3288a93 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_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]) 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_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_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_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_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_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_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_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_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_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( 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) diff --git a/axelrod/tests/strategies/test_averagecopier.py b/axelrod/tests/strategies/test_averagecopier.py index ae23667e7..8c162f94a 100644 --- a/axelrod/tests/strategies/test_averagecopier.py +++ b/axelrod/tests/strategies/test_averagecopier.py @@ -21,21 +21,25 @@ 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) - # Variable behaviour based on the history and stochastic - + def test_random_behavior1(self): actions = [ (C, C), (C, D), @@ -45,25 +49,27 @@ 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) + def test_random_behavior2(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), ] 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), @@ -73,24 +79,25 @@ 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) + def test_random_behavior4(self): opponent = axl.MockPlayer(actions=[C, C, C, D, D, D]) actions = [ (D, C), (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) @@ -109,16 +116,17 @@ class TestNiceAverageCopier(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_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) - # Tests that if opponent has played all D then player chooses D. + 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) - # Variable behaviour based on the history and stochastic behaviour + def test_random_behavior1(self): actions = [ (C, C), (C, D), @@ -126,27 +134,29 @@ 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) + def test_random_behavior2(self): actions = [ (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), ] 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), @@ -155,24 +165,25 @@ def test_strategy(self): (C, D), (D, D), (D, D), - (C, C), + (D, C), (D, C), (C, D), (D, D), ] 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), (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..40f09e80c 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): - actions = [(C, C)] * 41 + [(D, C)] + def test_stochastic_behavior(self): + actions = [(C, C)] * 13 + [(D, C)] self.versus_test(axl.Cooperator(), expected_actions=actions, seed=1) - actions = [(C, C)] * 16 + [(D, C)] + 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) @@ -163,19 +176,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 +198,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 +215,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 +235,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 +264,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) @@ -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): - actions = [(C, C), (C, C), (C, C), (C, C)] + def test_stochastic_behavior(self): + 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)] + 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): @@ -424,22 +436,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): @@ -455,11 +455,12 @@ class TestFirstByAnonymous(TestPlayer): "manipulates_state": False, } - def test_strategy(self): - actions = [(D, C), (C, C), (C, C), (D, C), (C, C), (C, C)] + 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) - actions = [(C, C), (C, C), (D, C), (C, C), (C, C), (D, C)] + 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) diff --git a/axelrod/tests/strategies/test_axelrod_second.py b/axelrod/tests/strategies/test_axelrod_second.py index 09cfc277d..26fa8078e 100644 --- a/axelrod/tests/strategies/test_axelrod_second.py +++ b/axelrod/tests/strategies/test_axelrod_second.py @@ -1,9 +1,6 @@ """Tests for the Second Axelrod strategies.""" -import random - import axelrod as axl -import numpy as np from .test_player import TestPlayer @@ -23,25 +20,22 @@ class TestChampion(TestPlayer): "manipulates_state": False, } - def test_strategy(self): + def test_initial_rounds(self): # Cooperates for first 10 rounds - - actions = [(C, C), (C, D)] * 5 # Cooperate for ten rounds - self.versus_test(axl.Alternator(), expected_actions=actions) - + actions = [(C, D)] * 10 + self.versus_test(axl.Defector(), 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) + actions += [(D, D)] * 7 + self.versus_test(axl.Defector(), expected_actions=actions) + def test_cooperate_until_defect(self): + # 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_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): @@ -58,24 +52,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_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=0) + self.versus_test(axl.Alternator(), expected_actions=actions, seed=10) + + 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_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=8) + self.versus_test(opponent, expected_actions=actions, seed=1) + + 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) @@ -171,12 +172,9 @@ class TestTranquilizer(TestPlayer): "manipulates_state": False, } - # test for initalised variables - - def test_init(self): - + 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) self.assertEqual(player.one_turn_after_good_defection_ratio, 5) @@ -185,7 +183,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 = { @@ -221,8 +218,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 @@ -234,9 +231,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 @@ -248,9 +246,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""" @@ -267,6 +266,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 = { @@ -277,9 +277,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""" @@ -293,9 +294,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) @@ -308,9 +310,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) @@ -323,9 +326,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) @@ -338,11 +342,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 = { @@ -545,10 +549,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 = [ @@ -560,70 +565,23 @@ 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_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=2) + self.versus_test(axl.Alternator(), expected_actions=actions, seed=10) + 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_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): @@ -689,109 +647,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 repond 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) - - # 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) - - # 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=2) + 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): @@ -836,6 +739,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), @@ -854,8 +759,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) @@ -996,7 +902,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 @@ -1007,6 +913,7 @@ def test_strategy(self): Defect37, expected_actions=actions, attrs={"mode": "Fair-weather"} ) + 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 @@ -1016,16 +923,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_exit_fair_weather2(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_non_fair_weather(self): # Opponent defects on 1st turn opponent_actions = [D] + [C] * 46 Defect1 = axl.MockPlayer(actions=opponent_actions) @@ -1038,8 +950,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_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) @@ -1048,8 +961,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_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) @@ -1063,6 +977,7 @@ def test_strategy(self): Defect1_38, expected_actions=actions, seed=2, attrs={"burned": True} ) + 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 @@ -1082,8 +997,9 @@ 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) + def test_parity_limit_shortening(self): # Test for parity limit shortening. opponent_actions = [D, C] * 1000 AsyncAlternator = axl.MockPlayer(actions=opponent_actions) @@ -1099,9 +1015,11 @@ def test_strategy(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 @@ -1118,52 +1036,21 @@ def test_strategy(self): axl.Defector(), expected_actions=actions, attrs={"recorded_defects": 119}, + seed=10 ) - # 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)] - random.seed(10) - player = self.player() - match = axl.Match((player, axl.Random()), turns=len(expected_actions)) - # 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_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, @@ -1355,6 +1242,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(), @@ -1363,11 +1251,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}, ) @@ -1389,11 +1278,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), @@ -1409,8 +1302,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) @@ -1428,59 +1324,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) - 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) - 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_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_defection_logic_not_triggered(self): + actions = [(C, C), (C, D)] * 10 + self.versus_test(axl.Alternator(), expected_actions=actions, seed=12) class TestBlack(TestPlayer): @@ -1500,6 +1360,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), @@ -1513,8 +1374,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), @@ -1528,7 +1390,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): @@ -1922,11 +1784,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_stochastic_behavior(self): opponent = axl.Defector() # Cooperate always the first 4 turns actions = [(C, D)] * 4 @@ -1951,82 +1884,6 @@ def test_strategy(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}) - - # 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=100) - # 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) - - # 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) 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_calculator.py b/axelrod/tests/strategies/test_calculator.py index 3b2dc66c8..0d5eedfb3 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): - """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)] + def test_twenty_rounds_joss_for_cyclers(self): + """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() 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): - """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 + def test_twenty_rounds_joss_then_defects_for_cyclers(self): + """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() + 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.FirstByJoss 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.FirstByJoss 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): diff --git a/axelrod/tests/strategies/test_cycler.py b/axelrod/tests/strategies/test_cycler.py index 74615e5fa..675d4b4d5 100644 --- a/axelrod/tests/strategies/test_cycler.py +++ b/axelrod/tests/strategies/test_cycler.py @@ -12,6 +12,7 @@ from .test_player import TestPlayer C, D = Action.C, Action.D +random = axl.RandomGenerator() class TestAntiCycler(TestPlayer): @@ -29,10 +30,10 @@ class TestAntiCycler(TestPlayer): } def test_has_no_cycles(self): - 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,49 @@ 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, + seed=1, # to prevent warning for unset seed ) self.assertRaises( InsufficientParametersError, - self.player_class._normalize_parameters, + self.player_class, cycle="", + seed=1, # to prevent warning for unset seed ) self.assertRaises( InsufficientParametersError, - self.player_class._normalize_parameters, + self.player_class, 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._normalize_parameters(cycle=cycle), (cycle, len(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._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) def test_crossover_even_length(self): cycle1 = "C" * 6 cycle2 = "D" * 6 - cross_cycle = "CDDDDD" + cross_cycle = "CCCCCD" - player1 = self.player_class(cycle=cycle1) - player2 = self.player_class(cycle=cycle2) - axl.seed(3) + 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 = "CDDDDDD" + cross_cycle = "CCCCCDD" - player1 = self.player_class(cycle=cycle1) - player2 = self.player_class(cycle=cycle2) - axl.seed(3) + 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 7a4c6b2d2..6ed840b0b 100644 --- a/axelrod/tests/strategies/test_evolvable_player.py +++ b/axelrod/tests/strategies/test_evolvable_player.py @@ -1,5 +1,4 @@ import functools -import random import unittest import axelrod as axl @@ -12,8 +11,12 @@ 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 __init__ = functools.partialmethod( cls.__init__, **kwargs) @@ -23,12 +26,12 @@ class PartialedClass(cls): class EvolvableTestOpponent(axl.EvolvablePlayer): name = "EvolvableTestOpponent" - def __init__(self, value=None): - super().__init__() + def __init__(self, value=None, seed=1): + 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) @@ -37,7 +40,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): @@ -48,13 +51,18 @@ 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 init_parameters = dict() - def player(self): - return self.player_class(**self.init_parameters) + def player(self, seed=1): + params = self.init_parameters.copy() + if "seed" not in params: # pragma: no cover + params["seed"] = seed + return self.player_class(**params) def test_repr(self): """Test that the representation is correct.""" @@ -74,14 +82,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=1) + player2 = self.player(seed=1) self.assertEqual(player1, player2) - for seed_ in range(2, 20): - axl.seed(seed_) - player2 = self.player() + + for seed_ in range(2, 200): + 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. @@ -91,30 +97,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=1) 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=1) + 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) @@ -133,8 +138,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=1) serialized = player.serialize_parameters() deserialized_player = player.__class__.deserialize_parameters(serialized) self.assertEqual(player, deserialized_player) @@ -142,8 +146,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=1) serialized = player.serialize_parameters() s = "0, 1, {}, 3".format(serialized) s2 = s.split(',')[2] @@ -151,17 +154,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]: - axl.seed(0) opponent = opponent_class() - match = axl.Match((player1.clone(), opponent)) + match = axl.Match((player1.clone(), opponent), seed=seed) results1 = match.play() - axl.seed(0) opponent = opponent_class() - match = axl.Match((player2.clone(), opponent)) + match = axl.Match((player2.clone(), opponent), seed=seed) results2 = match.play() self.assertEqual(results1, results2) @@ -171,7 +172,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) @@ -180,6 +181,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): @@ -192,22 +212,22 @@ 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=5) + 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=1) + 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=1) + 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=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 90815f780..f3525b0e1 100644 --- a/axelrod/tests/strategies/test_finite_state_machines.py +++ b/axelrod/tests/strategies/test_finite_state_machines.py @@ -1,6 +1,4 @@ """Tests for Finite State Machine Strategies.""" - -import random import unittest import axelrod as axl @@ -16,6 +14,7 @@ from .test_player import TestPlayer C, D = axl.Action.C, axl.Action.D +random = axl.RandomGenerator() class TestSimpleFSM(unittest.TestCase): @@ -1053,12 +1052,14 @@ class TestEvolvableFSMPlayer(unittest.TestCase): def test_normalized_parameters(self): self.assertRaises( InsufficientParametersError, - self.player_class._normalize_parameters + self.player_class, + seed=1, # To prevent exception from unset seed. ) self.assertRaises( InsufficientParametersError, - self.player_class._normalize_parameters, - transitions=[[0, C, 1, D], [0, D, 0, D], [1, C, 1, C], [1, D, 1, D]] + self.player_class, + 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): @@ -1066,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) @@ -1076,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) @@ -1087,11 +1089,18 @@ 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)) + 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" @@ -1133,9 +1142,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): diff --git a/axelrod/tests/strategies/test_gambler.py b/axelrod/tests/strategies/test_gambler.py index e7dc02893..cc58083e3 100755 --- a/axelrod/tests/strategies/test_gambler.py +++ b/axelrod/tests/strategies/test_gambler.py @@ -1,7 +1,6 @@ """Test for the Gambler strategy. Most tests come from the LookerUp test suite. """ import copy -import random import unittest import axelrod as axl @@ -14,6 +13,7 @@ tables = load_pso_tables("pso_gambler.csv", directory="data") C, D = axl.Action.C, axl.Action.D +random = axl.RandomGenerator() class TestGambler(TestPlayer): @@ -48,7 +48,7 @@ def test_stochastic_values(self): axl.Cooperator(), expected_actions=expected_actions, init_kwargs={"lookup_dict": stochastic_lookup}, - seed=1, + seed=80, ) @@ -83,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, ) @@ -123,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 ) @@ -239,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), @@ -261,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, ) @@ -366,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), @@ -391,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, ) @@ -448,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), @@ -462,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), @@ -479,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): @@ -496,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']) @@ -508,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) @@ -520,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) @@ -528,8 +556,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..38491fe08 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,11 +60,11 @@ 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 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 7c6c1fcae..e60d4e3dd 100644 --- a/axelrod/tests/strategies/test_hmm.py +++ b/axelrod/tests/strategies/test_hmm.py @@ -1,6 +1,4 @@ """Tests for Hidden Markov Model Strategies.""" - -import random import unittest import axelrod as axl @@ -10,7 +8,6 @@ HMMPlayer, SimpleHMM, is_stochastic_matrix, - random_vector, ) from .test_evolvable_player import PartialClass, TestEvolvablePlayer @@ -51,8 +48,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) @@ -72,8 +69,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) @@ -93,14 +90,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] @@ -114,8 +111,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]) @@ -129,18 +126,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] @@ -192,17 +193,17 @@ 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): 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): @@ -217,30 +218,45 @@ def test_normalized_parameters(self): initial_action = C self.assertRaises( - InsufficientParametersError, self.player_class._normalize_parameters + InsufficientParametersError, + self.player_class, + seed=1 + ) + self.assertRaises( + InsufficientParametersError, + self.player_class, + transitions_C=transitions_C, + transitions_D=transitions_D, + emission_probabilities=emission_probabilities, + initial_state=None, + seed=1 ) 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, + seed=1 ) self.assertRaises( InsufficientParametersError, - self.player_class._normalize_parameters, + self.player_class, initial_state=initial_state, initial_action=initial_action, + seed=1 ) 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()) - player = self.player_class(num_states=num_states) + vector.append(rng.random()) + player = self.player_class(num_states=num_states, seed=1) player.receive_vector(vector=vector) self.assertIsInstance(player, self.player_class) @@ -248,7 +264,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_hunter.py b/axelrod/tests/strategies/test_hunter.py index 6f2aebaf4..60a377a07 100644 --- a/axelrod/tests/strategies/test_hunter.py +++ b/axelrod/tests/strategies/test_hunter.py @@ -1,9 +1,8 @@ """Tests for the Hunter strategy.""" - -import random import unittest import axelrod as axl +from axelrod import Match from axelrod.strategies.hunter import detect_cycle from .test_player import TestPlayer @@ -135,11 +134,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(), @@ -147,8 +145,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): @@ -182,11 +180,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(), @@ -194,8 +191,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): @@ -259,8 +256,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_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_lookerup.py b/axelrod/tests/strategies/test_lookerup.py index 4dc226d35..c48474388 100755 --- a/axelrod/tests/strategies/test_lookerup.py +++ b/axelrod/tests/strategies/test_lookerup.py @@ -1,7 +1,6 @@ """Test for the Looker Up strategy.""" import copy -import random import unittest import axelrod as axl @@ -19,7 +18,7 @@ from .test_player import TestPlayer C, D = axl.Action.C, axl.Action.D - +random = axl.RandomGenerator() class TestLookupTable(unittest.TestCase): lookup_dict = { @@ -666,18 +665,22 @@ 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, + seed=1 ) self.assertRaises( InsufficientParametersError, - self.player_class._normalize_parameters, + self.player_class, pattern=pattern, initial_actions=initial_actions, + seed=1 ) self.assertRaises( InsufficientParametersError, - self.player_class._normalize_parameters, + self.player_class, lookup_dict=lookup_dict, + seed=1 ) diff --git a/axelrod/tests/strategies/test_memoryone.py b/axelrod/tests/strategies/test_memoryone.py index a2c8e879f..01e720618 100644 --- a/axelrod/tests/strategies/test_memoryone.py +++ b/axelrod/tests/strategies/test_memoryone.py @@ -5,14 +5,18 @@ import axelrod as axl from axelrod.strategies.memoryone import MemoryOnePlayer +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 class TestWinStayLoseShift(TestPlayer): - name = "Win-Stay Lose-Shift: C" + name = "Win-Stay Lose-Shift" player = axl.WinStayLoseShift expected_classifier = { "memory_depth": 1, @@ -25,7 +29,7 @@ class TestWinStayLoseShift(TestPlayer): } 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) @@ -65,8 +69,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) @@ -101,13 +106,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) @@ -137,16 +143,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): @@ -165,16 +174,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() @@ -236,6 +248,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) @@ -257,6 +270,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) @@ -285,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, + } diff --git a/axelrod/tests/strategies/test_memorytwo.py b/axelrod/tests/strategies/test_memorytwo.py index a619a8d4c..339aef728 100644 --- a/axelrod/tests/strategies/test_memorytwo.py +++ b/axelrod/tests/strategies/test_memorytwo.py @@ -1,12 +1,14 @@ """Tests for the Memorytwo strategies.""" -import random import unittest import warnings import axelrod as axl from axelrod.strategies.memorytwo import MemoryTwoPlayer +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 @@ -83,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, @@ -93,10 +95,10 @@ class TestMemoryStochastic(TestPlayer): } def test_strategy(self): - axl.seed(0) - vector = [random.random() for _ in range(16)] + rng = axl.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, @@ -104,7 +106,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, @@ -112,7 +114,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, @@ -120,7 +122,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, @@ -250,3 +252,54 @@ def test_strategy(self): expected_actions=actions, attrs={"play_as": "ALLD", "shift_counter": -1, "alld_counter": 2}, ) + + +class TestMemoryTwoCooperator(TestCooperator): + """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 = { + "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 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 = { + "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 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)) + expected_classifier = { + "memory_depth": 1, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } diff --git a/axelrod/tests/strategies/test_meta.py b/axelrod/tests/strategies/test_meta.py index 5bcb348a9..6ec22db6b 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 @@ -63,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() @@ -81,15 +82,18 @@ 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) + 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" @@ -148,6 +152,41 @@ def test_strategy(self): self.assertEqual(P1.strategy(P2), C) +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] + 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 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)) + + class TestNiceMetaWinner(TestMetaPlayer): name = "Nice Meta Winner" player = axl.NiceMetaWinner @@ -181,20 +220,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) @@ -211,6 +250,7 @@ class TestNiceMetaWinnerEnsemble(TestMetaPlayer): "manipulates_state": False, } + def test_strategy(self): actions = [(C, C)] * 8 self.versus_test( @@ -218,6 +258,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(), @@ -241,7 +283,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) @@ -347,7 +389,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): @@ -365,7 +407,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): @@ -385,6 +427,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) @@ -404,7 +447,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): @@ -422,7 +465,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): @@ -440,7 +483,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): @@ -476,7 +519,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): @@ -492,12 +535,58 @@ 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): + # 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] - P1 = axl.MetaMixer(team=team, distribution=distribution) - P2 = axl.Cooperator() actions = [(C, C)] * 20 self.versus_test( opponent=axl.Cooperator(), @@ -526,8 +615,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) @@ -550,7 +639,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): @@ -568,7 +657,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): @@ -586,7 +675,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): @@ -604,7 +693,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): @@ -622,7 +711,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): @@ -642,26 +731,28 @@ 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() 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)] @@ -672,6 +763,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( @@ -681,6 +773,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 @@ -718,3 +811,14 @@ def test_strategy(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_mindreader.py b/axelrod/tests/strategies/test_mindreader.py index 21a78ec6a..cbe903083 100644 --- a/axelrod/tests/strategies/test_mindreader.py +++ b/axelrod/tests/strategies/test_mindreader.py @@ -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 0f3c211e2..ec9fe051c 100644 --- a/axelrod/tests/strategies/test_player.py +++ b/axelrod/tests/strategies/test_player.py @@ -1,18 +1,17 @@ import itertools import pickle -import random import types import unittest +import warnings import axelrod as axl import numpy as np -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 @@ -57,14 +56,22 @@ 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 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) @@ -74,7 +81,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 @@ -100,16 +108,6 @@ def test_state_distribution(self): {(C, C): 1, (C, D): 1, (D, C): 2, (D, D): 1}, ) - 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) - self.assertEqual(player1.history[0], D) - self.assertEqual(player2.history[0], D) - def test_update_history(self): player = axl.Player() self.assertEqual(player.history, []) @@ -140,12 +138,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) @@ -414,9 +409,8 @@ 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), turns=10, seed=seed) + m.play() self.assertEqual(p1, p2) p1 = pickle.loads(pickle.dumps(p1)) p2 = pickle.loads(pickle.dumps(p2)) @@ -426,7 +420,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() @@ -436,7 +430,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)) @@ -450,13 +444,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) @@ -467,38 +458,34 @@ def test_reset_clone(self): clone = player.clone() self.assertEqual(player, clone) - @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", "Mind Bender", "Mind Controller", "Mind Warper"]: # 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 = 50 - r = random.random() for op in [ axl.Cooperator(), axl.Defector(), axl.TitForTat(), - axl.Random(p=r), + axl.Random(p=0.5), ]: - player1.reset() - player2.reset() - for p in [player1, player2]: - axl.seed(seed) - m = axl.Match((p, op), turns=turns) - 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( @@ -507,7 +494,7 @@ def test_clone(self, seed): 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. @@ -543,6 +530,7 @@ def versus_test( self, opponent, expected_actions, + turns=None, noise=None, seed=None, match_attributes=None, @@ -563,9 +551,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 +562,23 @@ 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), + test_match = TestMatch() + test_match.versus_test( + player, + opponent, + [x for (x, y) in expected_actions], + [y for (x, y) in expected_actions], turns=turns, 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 @@ -646,28 +627,53 @@ def versus_test( player2, expected_actions1, expected_actions2, + turns=None, 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: - axl.seed(seed) - turns = len(expected_actions1) - match = axl.Match((player1, player2), turns=turns, noise=noise) + raise ValueError("Mismatched Expected History in TestMatch.") + if not turns: + turns = len(expected_actions1) + + match = axl.Match( + (player1, player2), turns=turns, noise=noise, seed=seed, + 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 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: + for attr, value in attrs.items(): + self.assertEqual(getattr(player1, attr), value) + + def search_seeds(self, *args, **kwargs): # pragma: no cover + """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): + 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""" + match up.""" with self.assertRaises(ValueError): p1, p2 = axl.Cooperator(), axl.Cooperator() actions1 = [C, C] @@ -693,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. @@ -702,8 +707,7 @@ 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) + 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/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}, ) diff --git a/axelrod/tests/strategies/test_punisher.py b/axelrod/tests/strategies/test_punisher.py index 77a8db244..f2fabfb60 100644 --- a/axelrod/tests/strategies/test_punisher.py +++ b/axelrod/tests/strategies/test_punisher.py @@ -154,8 +154,8 @@ def test_strategy(self): class TestTrickyLevelPunisher(TestPlayer): - name = "Level Punisher" - player = axl.LevelPunisher + name = "Tricky Level Punisher" + player = axl.TrickyLevelPunisher expected_classifier = { "memory_depth": float("inf"), # Long memory "stochastic": False, @@ -171,24 +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 - 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 + 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) diff --git a/axelrod/tests/strategies/test_qlearner.py b/axelrod/tests/strategies/test_qlearner.py index 81a261109..948c8c02e 100644 --- a/axelrod/tests/strategies/test_qlearner.py +++ b/axelrod/tests/strategies/test_qlearner.py @@ -1,7 +1,5 @@ """Tests for the QLearner strategies.""" -import random - import axelrod as axl from .test_player import TestPlayer @@ -34,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}, @@ -68,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}, @@ -102,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}, @@ -136,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}, diff --git a/axelrod/tests/strategies/test_rand.py b/axelrod/tests/strategies/test_rand.py index 76bfb3478..c9341ab87 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_behavior2(self): 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() - 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]: 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..d45730247 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 @@ -22,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)] @@ -47,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), @@ -78,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) diff --git a/axelrod/tests/strategies/test_stalker.py b/axelrod/tests/strategies/test_stalker.py index cc013543c..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,12 +86,3 @@ def test_strategy(self): expected_actions=actions, match_attributes={"length": 4}, ) - - def test_reset(self): - axl.seed(0) - player = axl.Stalker() - m = axl.Match((player, axl.Alternator())) - m.play() - self.assertNotEqual(player.current_score, 0) - player.reset() - self.assertEqual(player.current_score, 0) diff --git a/axelrod/tests/strategies/test_titfortat.py b/axelrod/tests/strategies/test_titfortat.py index 0dc978eaf..fb984ea4b 100644 --- a/axelrod/tests/strategies/test_titfortat.py +++ b/axelrod/tests/strategies/test_titfortat.py @@ -1,11 +1,9 @@ """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 @@ -31,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( @@ -50,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, D), (D, C), (C, C), (C, D)] - self.versus_test(axl.Random(), expected_actions=actions, seed=0) + actions = [(C, D), (D, C), (C, C), (C, D), (D, D)] + self.versus_test(axl.Random(), expected_actions=actions, seed=17) - actions = [(C, C), (C, D), (D, D), (D, C)] - self.versus_test(axl.Random(), expected_actions=actions, seed=1) + def test_vs_random2(self): + actions = [(C, C), (C, C), (C, C), (C, C)] + 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]) @@ -145,7 +147,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. @@ -483,8 +485,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) @@ -667,9 +668,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=75) results = tournament.play(progress_bar=False) scores = [ round(average_score_per_turn * 1000, 1) @@ -709,56 +709,51 @@ 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), - 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() + 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): - ctft = 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, []) + def test_strategy_with_noise1(self): + self.versus_test(axl.Defector(), [(C, D)], turns=1, seed=9, + attrs={"_recorded_history": [C]}) - random.seed(0) - ctft.play(opponent, noise=0.9) - self.assertEqual(ctft.history, [D]) - self.assertEqual(ctft._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 - 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) + actions = list(zip([D, C], [C, D])) + 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 - 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) + actions = list(zip([D, C, C], [C, D, D])) + self.versus_test(axl.Defector(), actions, turns=3, noise=0.5, seed=49, + attrs={"_recorded_history": [C, C, C], + "contrite": False}) - # 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) + 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=158, + attrs={"_recorded_history": [C, C, C, D], + "contrite": False}) class TestAdaptiveTitForTat(TestPlayer): @@ -956,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, D), (D, C), (C, C), (C, D)] - self.versus_test( - axl.Random(), expected_actions=actions, seed=0, init_kwargs=init_kwargs - ) - actions = [(C, C), (C, D), (D, D), (D, C)] - self.versus_test( - axl.Random(), expected_actions=actions, seed=1, 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]) @@ -1056,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 = 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 = { + "memory_depth": 2, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + +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 = { + "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 @@ -1078,7 +1082,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 +1091,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 +1099,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 +1108,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 +1117,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)] @@ -1171,7 +1175,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/strategies/test_worse_and_worse.py b/axelrod/tests/strategies/test_worse_and_worse.py index a402a5e15..1fd2d5ef8 100644 --- a/axelrod/tests/strategies/test_worse_and_worse.py +++ b/axelrod/tests/strategies/test_worse_and_worse.py @@ -22,17 +22,17 @@ 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=8) + self.versus_test(axl.Cooperator(), expected_actions=actions, seed=144) - # 6 Rounds Cooperate and Defect no matter oponent + def test_strategy2(self): + # 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=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 +56,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 +66,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 +98,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 +149,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, ) 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) diff --git a/axelrod/tests/unit/test_eigen.py b/axelrod/tests/unit/test_eigen.py index f95c57e83..34a650f72 100644 --- a/axelrod/tests/unit/test_eigen.py +++ b/axelrod/tests/unit/test_eigen.py @@ -2,7 +2,7 @@ import unittest -import numpy +import numpy as np from axelrod.eigen import _normalise, principal_eigenvector from numpy.testing import assert_array_almost_equal @@ -10,41 +10,41 @@ 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 e68e015e6..9c727f91e 100644 --- a/axelrod/tests/unit/test_fingerprint.py +++ b/axelrod/tests/unit/test_fingerprint.py @@ -9,7 +9,6 @@ import numpy as np 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 @@ -244,11 +243,10 @@ 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]]) + 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) @@ -269,114 +267,115 @@ 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, - Point(x=0.0, y=0.5): 1.440, - Point(x=0.0, y=0.75): 1.080, - 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.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.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=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=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) + 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, - Point(x=0.0, y=0.5): 1.130, - Point(x=0.0, y=0.75): 1.050, - 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.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.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=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=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) - 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, - Point(x=0.0, y=0.5): 1.130, - Point(x=0.0, y=0.75): 1.030, - 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.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.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=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=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) - 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 @@ -384,21 +383,41 @@ def test_pair_fingerprints(self, strategy_pair): """ 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=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) + 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) + 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_history.py b/axelrod/tests/unit/test_history.py index 3eda019ce..e7b105c31 100644 --- a/axelrod/tests/unit/test_history.py +++ b/axelrod/tests/unit/test_history.py @@ -67,8 +67,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_makes_use_of.py b/axelrod/tests/unit/test_makes_use_of.py index 5f41b06de..ac5e6c1c7 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"}) diff --git a/axelrod/tests/unit/test_match.py b/axelrod/tests/unit/test_match.py index 73dcfec8a..84040eaa5 100644 --- a/axelrod/tests/unit/test_match.py +++ b/axelrod/tests/unit/test_match.py @@ -3,9 +3,10 @@ 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 assume, floats, integers +from hypothesis.strategies import floats, integers C, D = axl.Action.C, axl.Action.D @@ -79,16 +80,15 @@ def test_example_prob_end(self): outcomes """ p1, p2 = axl.Cooperator(), axl.Cooperator() - match = axl.Match((p1, p2), prob_end=0.5) - expected_lengths = [3, 1, 5] + expected_lengths = [2, 1, 1] for seed, expected_length in zip(range(3), expected_lengths): - axl.seed(seed) + 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) 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) @@ -117,11 +117,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) @@ -133,11 +130,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) @@ -360,16 +354,17 @@ 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), ]: - axl.seed(seed) - self.assertEqual(axl.match.sample_length(prob_end), expected_length) + rng = RandomGenerator(seed) + r = rng.random() + self.assertEqual(axl.match.sample_length(prob_end, r), expected_length) def test_sample_with_0_prob(self): - self.assertEqual(axl.match.sample_length(0), float("inf")) + self.assertEqual(axl.match.sample_length(0, 0.4), float("inf")) def test_sample_with_1_prob(self): - self.assertEqual(axl.match.sample_length(1), 1) + 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 d96b62fd6..e91a7eba8 100644 --- a/axelrod/tests/unit/test_match_generator.py +++ b/axelrod/tests/unit/test_match_generator.py @@ -3,7 +3,7 @@ import axelrod as axl 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, @@ -166,12 +166,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) @@ -179,6 +179,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 = axl.MatchGenerator( + players=self.players, + turns=test_turns, + game=test_game, + repetitions=repetitions, + seed=seed + ) + chunks1 = list(rr1.build_match_chunks()) + rr2 = axl.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 = axl.MatchGenerator( + players=self.players, + turns=test_turns, + game=test_game, + repetitions=repetitions, + seed=0 + ) + chunks1 = list(rr1.build_match_chunks()) + rr2 = axl.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) @@ -194,7 +235,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 9ea8c9903..8018683c6 100644 --- a/axelrod/tests/unit/test_moran.py +++ b/axelrod/tests/unit/test_moran.py @@ -1,15 +1,16 @@ import itertools -import random import unittest from collections import Counter import axelrod as axl import matplotlib.pyplot as plt -from axelrod.moran import fitness_proportionate_selection +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() class TestMoranProcess(unittest.TestCase): @@ -62,50 +63,45 @@ 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=2) self.assertEqual(mp.mutate(0), players[2]) - axl.seed(4) + 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 = 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(), 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) - axl.seed(5) + mp = MoranProcess(players, mutation_rate=0.5, mode="db", seed=9) self.assertEqual(mp.death(), 1) self.assertEqual(mp.dead, 1) - axl.seed(2) - 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 = axl.MoranProcess(players, mode="bd", interaction_graph=graph) - axl.seed(1) - self.assertEqual(mp.death(0), 0) - axl.seed(5) + mp = MoranProcess(players, mode="bd", interaction_graph=graph, seed=1) self.assertEqual(mp.death(0), 1) - axl.seed(2) + 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) 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) - self.assertEqual(mp.death(), 0) + mp = MoranProcess(players, mode="db", seed=1) + 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 = axl.MoranProcess(players, mode="bd") - axl.seed(1) + mp = MoranProcess(players, mode="bd", seed=2) self.assertEqual(mp.birth(), 0) def test_fixation_check(self): @@ -133,10 +129,11 @@ def test_matchup_indices(self): 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) + 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]), 2) + self.assertEqual(mp.fitness_proportionate_selection([1, 1, 1]), 0) def test_exit_condition(self): p1, p2 = axl.Cooperator(), axl.Cooperator() @@ -144,42 +141,56 @@ 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() - 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) + self.assertEqual(len(mp), 2) + self.assertEqual(len(populations), 2) self.assertEqual(populations, mp.populations) self.assertEqual(mp.winning_strategy_name, str(p2)) 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) + self.assertEqual(len(mp), 2) + self.assertEqual(len(populations), 2) self.assertEqual(populations, mp.populations) self.assertEqual(mp.winning_strategy_name, str(p1)) 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)) + self.assertEqual(mp.winning_strategy_name, str(p2)) def test_death_birth(self): """Two player death-birth should fixate after one round.""" 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. @@ -195,30 +206,26 @@ 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) 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() - 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 @@ -243,8 +250,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) @@ -274,17 +280,15 @@ 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) + self.assertEqual(len(mp), 8) + self.assertEqual(len(populations), 8) self.assertEqual(populations, mp.populations) 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]) @@ -298,11 +302,10 @@ 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) + 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) @@ -313,21 +316,20 @@ 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() 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() @@ -335,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) @@ -347,22 +349,21 @@ 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] 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): - axl.seed(689) players = ( axl.Cooperator(), axl.Defector(), @@ -371,48 +372,51 @@ 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=3419 ) 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) + players = [axl.EvolvableFSMPlayer(num_states=2, initial_state=1, initial_action=C, + seed=4) for _ in range(5)] - mp = axl.MoranProcess(players, turns=10, mutation_method="atomic") - population = mp.play() + mp = MoranProcess(players, turns=10, mutation_method="atomic", seed=12) + 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): - axl.seed(10) cycle_length = 5 - players = [axl.EvolvableCycler(cycle_length=cycle_length) + players = [axl.EvolvableCycler(cycle_length=cycle_length, seed=4) for _ in range(5)] - mp = axl.MoranProcess(players, turns=10, mutation_method="atomic") - 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) + mp = MoranProcess(players, turns=10, mutation_method="atomic", seed=10) + 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): - axl.seed(10) 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): - 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) @@ -425,17 +429,22 @@ 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()) 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=interaction_graph, + reproduction_graph=reproduction_graph, + seed=seed) mp.play() winner2 = mp.winning_strategy_name self.assertEqual(winner, winner2) @@ -443,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), (8, False)] + seeds = [(1, True), (3, False)] players = [] N = 6 graph = axl.graph.cycle(N) @@ -452,12 +461,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) @@ -465,7 +472,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), (5, False)] players = [] N = 6 graph1 = axl.graph.cycle(N) @@ -475,15 +482,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 @@ -492,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), (5, False)] + seeds = [(1, True), (3, False)] players = [] N = 6 graph = axl.graph.cycle(N) @@ -501,12 +508,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_pickling.py b/axelrod/tests/unit/test_pickling.py index 757917212..29f25d645 100644 --- a/axelrod/tests/unit/test_pickling.py +++ b/axelrod/tests/unit/test_pickling.py @@ -1,11 +1,10 @@ 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. @@ -218,12 +217,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 +240,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) @@ -258,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 506f7adef..19a84c2b0 100644 --- a/axelrod/tests/unit/test_random_.py +++ b/axelrod/tests/unit/test_random_.py @@ -1,54 +1,70 @@ """Tests for the random functions.""" - -import random import unittest from collections import Counter import axelrod as axl -import numpy +from axelrod import BulkRandomGenerator, Pdf, RandomGenerator C, D = axl.Action.C, axl.Action.D -class TestRandom_(unittest.TestCase): +class TestRandomGenerator(unittest.TestCase): def test_return_values(self): - 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]) + # 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(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 affected.""" + random = RandomGenerator() for p in [0, 1]: - axl.seed(0) + random.seed(0) r = random.random() - axl.seed(0) - axl.random_choice(p) + random.seed(0) + random.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 = 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) class TestPdf(unittest.TestCase): @@ -67,8 +83,8 @@ def test_init(self): def test_sample(self): """Test that sample maps to correct domain""" all_samples = [] - - axl.seed(0) + random = RandomGenerator() + random.seed(0) for sample in range(100): all_samples.append(self.pdf.sample()) @@ -79,7 +95,7 @@ def test_seed(self): """Test that numpy seeds the sample properly""" for s in range(10): - 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()) diff --git a/axelrod/tests/unit/test_resultset.py b/axelrod/tests/unit/test_resultset.py index 335d65360..97563d3e9 100644 --- a/axelrod/tests/unit/test_resultset.py +++ b/axelrod/tests/unit/test_resultset.py @@ -494,9 +494,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) @@ -1219,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() diff --git a/axelrod/tests/unit/test_strategy_transformers.py b/axelrod/tests/unit/test_strategy_transformers.py index 7b909ab22..7539ae8cd 100644 --- a/axelrod/tests/unit/test_strategy_transformers.py +++ b/axelrod/tests/unit/test_strategy_transformers.py @@ -1,8 +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_defector import TestDefector +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 @@ -18,7 +18,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,14 +122,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 - for s in axl.strategies: - opponent = axl.Cooperator() - player = IdentityTransformer()(s)() - player.play(opponent) - def test_naming(self): """Tests that the player and class names are properly modified.""" cls = FlipTransformer()(axl.Cooperator) @@ -180,24 +173,179 @@ 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) + + 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_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""" + 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) - 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)]) + +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 @@ -213,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 @@ -226,11 +377,10 @@ def test_dual_transformer_simple_play_regression_test(self): DualTransformer()(axl.Cooperator) )() - for _ in range(3): - multiple_dual_transformers.play(dual_transformer_not_first) - - 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. @@ -247,62 +397,198 @@ 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): + +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) + + 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")) + + 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}) + + +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) + + 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.""" + 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]) + + p1 = axl.Alternator() + p2 = FlipTransformer()(axl.Alternator)() + self.versus_test(p1, p2, [C, D, C, D, C], [D, D, D, D, D]) + + +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) + + def test_stochastic_values_classifier(self): + p1 = ForgiverTransformer(0.5)(axl.Alternator)() + self.assertTrue(axl.Classifiers["stochastic"](p1)) + + def test_deterministic_values_classifier(self): + p1 = ForgiverTransformer(0)(axl.Alternator)() + self.assertFalse(axl.Classifiers["stochastic"](p1)) + + p1 = ForgiverTransformer(1)(axl.Alternator)() + self.assertFalse(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) + + 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) + + +class TestHistoryTrackingTransformer(TestMatch): + def test_history_track(self): + """Tests the tracked history matches.""" + p1 = axl.Cooperator() + p2 = TrackHistoryTransformer()(axl.Random)() + match = axl.Match((p1, p2), turns=6, seed=1) + match.play() + self.assertEqual(p2.history, p2._recorded_history) + + 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) + + +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_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) + turns = 100 + self.versus_test(axl.Cooperator(), Cooperator2(), [C] * turns, [C] * turns) + self.versus_test(axl.Cooperator(), Defector2(), [C] * turns, [D] * turns) - p1 = player_class() - p2 = DualTransformer()(player_class)() - p3 = axl.CyclerCCD() # Cycles 'CCD' - axl.seed(0) - for _ in range(turns): - p1.play(p3) +class TestInitialTransformer(TestMatch): + def test_initial_transformer(self): + """Tests the InitialTransformer.""" + p1 = axl.Cooperator() + self.assertEqual(axl.Classifiers["memory_depth"](p1), 0) + p2 = InitialTransformer([D, D])(axl.Cooperator)() + self.assertEqual(axl.Classifiers["memory_depth"](p2), 2) + match = axl.Match((p1, p2), turns=5, seed=0) + match.play() + self.assertEqual(p2.history, [D, D, C, C, C]) - p3.reset() + p1 = axl.Cooperator() + p2 = InitialTransformer([D, D, C, D])(axl.Cooperator)() + match = axl.Match((p1, p2), turns=5, seed=0) + match.play() + self.assertEqual(p2.history, [D, D, C, D, C]) - axl.seed(0) - for _ in range(turns): - p2.play(p3) + p3 = InitialTransformer([D, D])(axl.Adaptive)() + self.assertEqual(axl.Classifiers["memory_depth"](p3), float("inf")) - self.assertEqual(p1.history, [x.flip() for x in p2.history]) - def test_jossann_transformer(self): - """Tests the JossAnn transformer. - """ +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() - for _ in range(5): - p1.play(p2) - self.assertEqual(p1.history, [C, C, C, C, C]) + self.versus_test(p1, p2, [C] * 5, [C] * 5) 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_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) + 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() - for _ in range(5): - p1.play(p2) - self.assertEqual(p1.history, [D, D, D, D, D]) + self.versus_test(p1, p2, [D] * 5, [C] * 5) + def test_stochastic1(self): probability = (0.3, 0.3) p1 = JossAnnTransformer(probability)(axl.TitForTat)() self.assertTrue(axl.Classifiers["stochastic"](p1)) - p2 = axl.Cycler() - axl.seed(0) - for _ in range(5): - p1.play(p2) - self.assertEqual(p1.history, [D, C, C, D, D]) + self.versus_test(p1, p2, [D, C, C, D, D], [C, C, D, C, C], seed=18) + def test_stochastic2(self): 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) - self.assertEqual(p1.history, [D, C, D, D, C]) + self.versus_test(p1, p2, [D, C, D, D, C], [C] * 5, seed=27) + def test_stochastic_classifiers(self): probability = (0, 1) p1 = JossAnnTransformer(probability)(axl.Random) self.assertFalse(axl.Classifiers["stochastic"](p1())) @@ -321,380 +607,286 @@ def test_jossann_transformer(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) self.assertTrue(axl.Classifiers["stochastic"](p1())) - 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) - 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())) +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())) - p2 = NoisyTransformer(1)(axl.Cooperator) - self.assertFalse(axl.Classifiers["stochastic"](p2())) + p1 = MD() + p2 = axl.Cooperator() + self.versus_test(p1, p2, [C] * 5, [C] * 5) - p2 = NoisyTransformer(0.3)(axl.Cooperator) - self.assertTrue(axl.Classifiers["stochastic"](p2())) + probability = 0 + MD = MixedTransformer(probability, axl.Cooperator)(axl.Defector) + self.assertFalse(axl.Classifiers["stochastic"](MD())) - p2 = NoisyTransformer(0)(axl.Random) - self.assertTrue(axl.Classifiers["stochastic"](p2())) + p1 = MD() + p2 = axl.Cooperator() + self.versus_test(p1, p2, [D] * 5, [C] * 5) - p2 = NoisyTransformer(1)(axl.Random) - self.assertTrue(axl.Classifiers["stochastic"](p2())) + def test_mixed_transformer(self): + # Decorating with list and distribution + # Decorate a cooperator putting all weight on other strategies that are + # 'nice' + probability = [0.3, 0.2, 0] + strategies = [axl.TitForTat, axl.Grudger, axl.Defector] + MD = MixedTransformer(probability, strategies)(axl.Cooperator) + self.assertTrue(axl.Classifiers["stochastic"](MD())) - 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) - self.assertEqual(p1.history, [C, D, C, C, D, C, C, D, C, D]) - - p1 = ForgiverTransformer(0)(axl.Alternator)() - self.assertFalse(axl.Classifiers["stochastic"](p1)) + p1 = MD() + # Against a cooperator we see that we only cooperate + p2 = axl.Cooperator() + self.versus_test(p1, p2, [C] * 5, [C] * 5) - p1 = ForgiverTransformer(1)(axl.Alternator)() - self.assertFalse(axl.Classifiers["stochastic"](p1)) + # Decorate a cooperator putting all weight on Defector + probability = (0, 0, 1) # Note can also pass tuple + strategies = [axl.TitForTat, axl.Grudger, axl.Defector] + MD = MixedTransformer(probability, strategies)(axl.Cooperator) + self.assertFalse(axl.Classifiers["stochastic"](MD())) - def test_initial_transformer(self): - """Tests the InitialTransformer.""" - p1 = axl.Cooperator() - 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) - self.assertEqual(p2.history, [D, D, C, C, C]) + p1 = MD() + # Against a cooperator we see that we only defect + p2 = axl.Cooperator() + self.versus_test(p1, p2, [D] * 5, [C] * 5) - p1 = axl.Cooperator() - p2 = InitialTransformer([D, D, C, D])(axl.Cooperator)() - for _ in range(5): - p1.play(p2) - self.assertEqual(p2.history, [D, D, C, D, C]) - p3 = InitialTransformer([D, D])(axl.Adaptive)() - self.assertEqual(axl.Classifiers["memory_depth"](p3), float("inf")) +class TestNiceTransformer(TestMatch): + def test_nice1(self): + """Tests the NiceTransformer.""" + p1 = NiceTransformer()(axl.Defector)() + p2 = axl.Defector() + self.versus_test(p1, p2, [C, D, D, D, D], [D, D, D, D, D]) - 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([])) + def test_nice2(self): + p1 = NiceTransformer()(axl.Defector)() + p2 = axl.Alternator() + self.versus_test(p1, p2, [C, C, D, D, D], [C, D, C, D, C]) - p2.match_attributes["length"] = 6 - for _ in range(8): - p1.play(p2) - self.assertEqual(p2.history, [C, C, C, D, D, D, C, C]) + def test_nice3(self): + p1 = NiceTransformer()(axl.Defector)() + p2 = axl.Cooperator() + self.versus_test(p1, p2, [C, C, C, C, C], [C, C, C, 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.""" +class TestNoisyTransformer(TestMatch): + def test_noisy_transformer(self): + """Tests that the noisy transformed does flip some moves.""" + # Cooperator to Defector p1 = axl.Cooperator() - p2 = FinalTransformer([D, D])(axl.Cooperator)() - for _ in range(6): - p1.play(p2) - self.assertEqual(p2.history, [C, C, C, C, C, C]) + p2 = NoisyTransformer(0.5)(axl.Cooperator)() + self.assertTrue(axl.Classifiers["stochastic"](p2)) + self.versus_test(p1, p2, [C] * 10, [D, C, C, D, C, D, C, D, D, C], seed=1) - def test_history_track(self): - """Tests the history tracking transformer.""" - p1 = axl.Cooperator() - p2 = TrackHistoryTransformer()(axl.Random)() - for _ in range(6): - p1.play(p2) - self.assertEqual(p2.history, p2._recorded_history) + 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())) - 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() - p2 = axl.Cooperator() - p1.match_attributes["length"] = 8 - for _ in range(8): - p1.play(p2) - self.assertEqual(p1.history, [D, D, C, C, C, C, D, D]) + # Deterministic --> Stochastic + p2 = NoisyTransformer(0.3)(axl.Cooperator) + self.assertTrue(axl.Classifiers["stochastic"](p2())) - 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) - self.assertEqual(p1.history, [D, D, C, C, C, C, D, D]) + # Stochastic --> Deterministic, case 0 + p2 = NoisyTransformer(0)(axl.Random) + self.assertTrue(axl.Classifiers["stochastic"](p2())) + + # Stochastic --> Deterministic, case 1 + p2 = NoisyTransformer(1)(axl.Random) + self.assertTrue(axl.Classifiers["stochastic"](p2())) - def test_compose_transformers(self): - cls1 = compose_transformers( - FinalTransformer([D, D]), InitialTransformer([D, D]) - ) - p1 = cls1(axl.Cooperator)() - p2 = axl.Cooperator() - p1.match_attributes["length"] = 8 - for _ in range(8): - p1.play(p2) - self.assertEqual(p1.history, [D, D, C, C, C, C, D, D]) - def test_retailiation(self): +class TestRetailiateTransformer(TestMatch): + def test_retailiating_cooperator_against_defector(self): """Tests the RetaliateTransformer.""" p1 = RetaliationTransformer(1)(axl.Cooperator)() p2 = axl.Defector() - for _ in range(5): - p1.play(p2) - 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_retailiating_cooperator_against_alternator(self): p1 = RetaliationTransformer(1)(axl.Cooperator)() p2 = axl.Alternator() - for _ in range(5): - p1.play(p2) - self.assertEqual(p1.history, [C, C, D, C, D]) - self.assertEqual(p2.history, [C, D, C, D, C]) + 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() - for _ in range(9): - p1.play(p2) - 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]) + 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() - p1.play(p2) - p1.play(p2) - self.assertEqual(p1.history, [C, C]) + self.versus_test(p1, p2, [C, C], [C, C]) p1 = TFT() p2 = axl.Defector() - p1.play(p2) - p1.play(p2) - self.assertEqual(p1.history, [C, D]) + self.versus_test(p1, p2, [C, D], [D, D]) - random.seed(12) + def test_retaliation_until_apology_stochastic(self): + TFT = RetaliateUntilApologyTransformer()(axl.Cooperator) p1 = TFT() p2 = axl.Random() - for _ in range(5): - p1.play(p2) - self.assertEqual(p1.history, [C, C, D, D, C]) + self.versus_test(p1, p2, [C, C, D, D, C], [C, D, D, C, D], seed=1) - def test_apology(self): - """Tests the ApologyTransformer.""" - ApologizingDefector = ApologyTransformer([D], [C])(axl.Defector) - p1 = ApologizingDefector() - p2 = axl.Cooperator() - for _ in range(5): - p1.play(p2) - 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) - self.assertEqual(p1.history, [D, D, C, D, D, C]) - def test_mixed(self): - """Tests the MixedTransformer.""" - probability = 1 - MD = MixedTransformer(probability, axl.Cooperator)(axl.Defector) - self.assertFalse(axl.Classifiers["stochastic"](MD())) +# Run the standard Player tests on some specifically transformed players - p1 = MD() - p2 = axl.Cooperator() - for _ in range(5): - p1.play(p2) - self.assertEqual(p1.history, [C, C, C, C, C]) +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, + } - probability = 0 - MD = MixedTransformer(probability, axl.Cooperator)(axl.Defector) - self.assertFalse(axl.Classifiers["stochastic"](MD())) - p1 = MD() - p2 = axl.Cooperator() - for _ in range(5): - p1.play(p2) - self.assertEqual(p1.history, [D, D, D, D, D]) +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, + } - # Decorating with list and distribution - # Decorate a cooperator putting all weight on other strategies that are - # 'nice' - probability = [0.3, 0.2, 0] - strategies = [axl.TitForTat, axl.Grudger, axl.Defector] - MD = MixedTransformer(probability, strategies)(axl.Cooperator) - self.assertTrue(axl.Classifiers["stochastic"](MD())) - p1 = MD() - # Against a cooperator we see that we only cooperate - p2 = axl.Cooperator() - for _ in range(5): - p1.play(p2) - self.assertEqual(p1.history, [C, C, C, C, C]) +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, + } - # Decorate a cooperator putting all weight on Defector - probability = (0, 0, 1) # Note can also pass tuple - strategies = [axl.TitForTat, axl.Grudger, axl.Defector] - MD = MixedTransformer(probability, strategies)(axl.Cooperator) - self.assertFalse(axl.Classifiers["stochastic"](MD())) - p1 = MD() - # Against a cooperator we see that we only defect - p2 = axl.Cooperator() - for _ in range(5): - p1.play(p2) - self.assertEqual(p1.history, [D, D, D, D, D]) +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, + } - 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)() - for _ in range(4): - p1.play(p2) - 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))() - for _ in range(4): - p1.play(p2) - self.assertEqual(p1.history, [C, D, C, C]) - self.assertEqual(p2.history, [D, C, C, C]) - - def test_grudging(self): - """Test the GrudgeTransformer.""" - p1 = axl.Defector() - p2 = GrudgeTransformer(1)(axl.Cooperator)() - for _ in range(4): - p1.play(p2) - self.assertEqual(p1.history, [D, D, D, D]) - self.assertEqual(p2.history, [C, C, D, D]) +class TestFinalInitialTransformedCooperator(TestPlayer): + player = FinalTransformer([D, D])(InitialTransformer([D, D, D])(axl.Cooperator)) + name = "Final Initial 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, + } - p1 = InitialTransformer([C])(axl.Defector)() - p2 = GrudgeTransformer(2)(axl.Cooperator)() - for _ in range(8): - p1.play(p2) - 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): - """Tests the NiceTransformer.""" - p1 = NiceTransformer()(axl.Defector)() - p2 = axl.Defector() - for _ in range(5): - p1.play(p2) - self.assertEqual(p1.history, [C, D, D, D, D]) - self.assertEqual(p2.history, [D, D, D, D, D]) +class TestRUACooperatorisTFT(TestTitForTat): + player = RetaliateUntilApologyTransformer()(axl.Cooperator) + name = "RUA Cooperator" + expected_classifier = { + "memory_depth": 1, + "stochastic": False, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } - p1 = NiceTransformer()(axl.Defector)() - p2 = axl.Alternator() - for _ in range(5): - p1.play(p2) - 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) - self.assertEqual(p1.history, [C, C, C, C, C]) - self.assertEqual(p2.history, [C, C, C, C, C]) +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, + } - 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() - for i in range(5): - player.play(third_player) - transformed.play(clone) - 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))() - for i in range(5): - player.play(third_player) - transformed.play(clone) - self.assertEqual(player.history, transformed.history) +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, + } - 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") - for _ in range(5): - p1.play(p2) - 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) - self.assertEqual(p1.history, [C, D, C, D, C]) - self.assertEqual(p2.history, [D, D, D, D, D]) +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, + } -TFT = RetaliateUntilApologyTransformer()(axl.Cooperator) +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 TestRUAisTFT(TestTitForTat): - # This runs the 7 TFT tests when unittest is invoked - player = TFT - name = "RUA Cooperator" +class TestFullForgivingCooperatorIsDefector(TestCooperator): + name = "Forgiving Defector: 1" + player = ForgiverTransformer(1)(axl.Defector) expected_classifier = { - "memory_depth": 0, # really 1 + "memory_depth": 0, "stochastic": False, "makes_use_of": set(), "long_run_time": False, @@ -704,11 +896,113 @@ class TestRUAisTFT(TestTitForTat): } -# Test that FlipTransformer(Defector) == Cooperator -Cooperator2 = FlipTransformer()(axl.Defector) +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 TestFlipDefector(TestCooperator): - # This runs the 7 TFT tests when unittest is invoked - name = "Flipped Defector" - player = Cooperator2 +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, + } + + +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, + } diff --git a/axelrod/tests/unit/test_strategy_utils.py b/axelrod/tests/unit/test_strategy_utils.py index 4f849d621..b3a4f7ef0 100644 --- a/axelrod/tests/unit/test_strategy_utils.py +++ b/axelrod/tests/unit/test_strategy_utils.py @@ -1,5 +1,4 @@ """Tests for the strategy utils.""" - import unittest import axelrod as axl @@ -66,11 +65,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) @@ -78,8 +79,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 13b814554..f8cca4bb4 100644 --- a/axelrod/tests/unit/test_tournament.py +++ b/axelrod/tests/unit/test_tournament.py @@ -396,7 +396,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], @@ -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): @@ -654,7 +654,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 = {} @@ -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, @@ -745,6 +731,59 @@ 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. 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=10, + repetitions=100, + seed=seed + ) + tournament2 = axl.Tournament( + name=self.test_name, + players=players, + game=self.game, + turns=10, + repetitions=100, + seed=seed + ) + 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)] + 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 @@ -782,14 +821,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. @@ -797,12 +838,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): @@ -854,7 +901,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 @@ -870,16 +917,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) @@ -961,7 +1008,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 @@ -970,8 +1017,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 @@ -979,9 +1027,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) @@ -998,7 +1046,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 @@ -1009,10 +1057,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 26144757c..925fe1477 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 @@ -33,6 +33,7 @@ def __init__( noise: float = 0, edges: List[Tuple] = None, match_attributes: dict = None, + seed: int = None ) -> None: """ Parameters @@ -70,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 @@ -85,6 +87,7 @@ def __init__( noise=self.noise, edges=edges, match_attributes=match_attributes, + seed=self.seed ) self._logger = logging.getLogger(__name__) @@ -422,11 +425,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() 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/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..ce30d7298 100644 --- a/docs/reference/overview_of_strategies.rst +++ b/docs/reference/overview_of_strategies.rst @@ -427,9 +427,8 @@ 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=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)) diff --git a/docs/tutorials/advanced/classification_of_strategies.rst b/docs/tutorials/advanced/classification_of_strategies.rst index 89468d44a..155d1c57c 100644 --- a/docs/tutorials/advanced/classification_of_strategies.rst +++ b/docs/tutorials/advanced/classification_of_strategies.rst @@ -90,7 +90,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 @@ -101,7 +101,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:: diff --git a/docs/tutorials/advanced/setting_a_seed.rst b/docs/tutorials/advanced/setting_a_seed.rst index 58158c43c..cf3b16b45 100644 --- a/docs/tutorials/advanced/setting_a_seed.rst +++ b/docs/tutorials/advanced/setting_a_seed.rst @@ -4,30 +4,81 @@ 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 + +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 -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() +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. 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/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 diff --git a/docs/tutorials/further_topics/approximate_moran_processes.rst b/docs/tutorials/further_topics/approximate_moran_processes.rst index 9150df476..8bcc4eb5d 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=5) + >>> results = mp.play() + >>> mp.population_distribution() Counter({'Defector': 3}) 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. diff --git a/docs/tutorials/further_topics/fingerprinting.rst b/docs/tutorials/further_topics/fingerprinting.rst index 1f0845d7e..4370c02de 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... + 3.75 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:: 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..7f49e30cd 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() @@ -59,19 +59,23 @@ 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 Davis: 10 + 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 - First by Joss: 0.9 First by Tullock - Random: 0.5 + First by Joss: 0.9 First by Anonymous + Random: 0.5 + + +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 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 @@ -116,7 +120,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 +151,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 `1408` 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=1408, ... ) >>> results = tournament.play() >>> for name in results.ranked_names: @@ -162,41 +166,43 @@ 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 Joss: 0.9 First by Anonymous + Random: 0.5 -With `1238` the strategy submitted by Shubik wins:: - >>> axl.seed(1238) +With `136` the strategy submitted by Grofman wins:: + >>> tournament = axl.Tournament( ... players=first_tournament_participants_ordered_by_reported_rank, ... turns=200, - ... repetitions=5 + ... repetitions=5, + ... seed=136 ... ) >>> 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) + First by Nydegger Grudger First by Davis: 10 - First by Graaskamp: 0.05 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 + diff --git a/requirements.txt b/requirements.txt index 5c0d22cab..f9ca34c6c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,14 @@ -cloudpickle>=0.2.1 -fsspec>=0.4.3 -dask>=2.3.0 -matplotlib>=2.0.0 -numpy>=1.9.2 -pandas>=0.18.1 -pathlib>=1.0.1 -prompt-toolkit>=1.0.7 +# 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 +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 +scipy>=1.3.3 +tqdm>=4.39.0