diff --git a/.travis.yml b/.travis.yml index 6448c824b..2d62c2410 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ before_install: install: - pip install -r requirements.txt - pip install sphinx + - pip install sphinx_rtd_theme - pip install coverage - pip install coveralls script: diff --git a/axelrod/__init__.py b/axelrod/__init__.py index caf912c8f..d2059d7df 100644 --- a/axelrod/__init__.py +++ b/axelrod/__init__.py @@ -10,7 +10,7 @@ from .match import Match from .strategies import * from .tournament_type import * -from .tournament import Tournament +from .tournament import Tournament, ProbEndTournament from .tournament_manager import TournamentManager from .tournament_manager_factory import TournamentManagerFactory from .result_set import ResultSet diff --git a/axelrod/cooperation.py b/axelrod/cooperation.py deleted file mode 100644 index 86c29279e..000000000 --- a/axelrod/cooperation.py +++ /dev/null @@ -1,294 +0,0 @@ -from . import eigen -from axelrod import Actions -from axelrod.payoff import player_count - -C, D = Actions.C, Actions.D - - -def cooperation_matrix(interactions): - """ - The cooperation matrix from a single round robin. - - Parameters - ---------- - interactions : dictionary - A dictionary of the form: - - e.g. for a round robin between Cooperator, Defector and Alternator - with 2 turns per round: - { - (0, 0): [(C, C), (C, C)]. - (0, 1): [(C, D), (C, D)], - (0, 2): [(C, C), (C, D)], - (1, 1): [(D, D), (D, D)], - (1, 2): [(D, C), (D, D)], - (2, 2): [(C, C), (D, D)] - } - - i.e. the key is a pair of player index numbers and the value, a list of - plays. The list contains one pair per turn in the round robin. - The dictionary contains one entry for each combination of players. - - nplayers : integer - The number of players in the round robin - - Returns - ------- - list - The cooperation matrix (C) of the form: - - [ - [a, b, c], - [d, e, f], - [g, h, i], - ] - - i.e. an n by n matrix where n is the number of players. Each row (i) - and column (j) represents an individual player and the the value Cij - is the number of times player i cooperated against opponent j. - """ - nplayers = player_count(interactions) - cooperation = [[0 for i in range(nplayers)] for j in range(nplayers)] - for players, actions in interactions.items(): - p1_actions, p2_actions = zip(*actions) - p1_cooperation = p1_actions.count(C) - p2_cooperation = p2_actions.count(C) - cooperation[players[0]][players[1]] = p1_cooperation - if players[0] != players[1]: - cooperation[players[1]][players[0]] = p2_cooperation - return cooperation - - -def cooperation(results): - """ - The total cooperation matrix from a tournament of multiple repetitions. - - Parameters - ---------- - results : list - A matrix of the form: - - [ - [[a, j], [b, k], [c, l]], - [[d, m], [e, n], [f, o]], - [[g, p], [h, q], [i, r]], - ] - - i.e. one row per player, containing one element per opponent (in - order of player index) which lists cooperation values for each - repetition. - - Returns - ------- - list - The cooperation matrix (C) of the form: - - [ - [[a + j], [b + k], [c + l]], - [[d + m], [e + n], [f + o]], - [[g + p], [h + q], [i + r]], - ] - - i.e. an n by n matrix where n is the number of players. Each row (i) - and column (j) represents an individual player and the the value Cij - is the number of times player i cooperated against opponent j. - """ - return[[sum(element) for element in row] for row in results] - - -def normalised_cooperation(cooperation, turns, repetitions): - """ - The per-turn normalised cooperation matrix for a tournament of n repetitions. - - Parameters - ---------- - cooperation : list - The cooperation matrix (C) - turns : integer - The number of turns in each round robin. - repetitions : integer - The number of repetitions in the tournament. - - Returns - ------- - list - A matrix (N) such that: - - N = C / t - - where t is the total number of turns played in the tournament. - """ - turns = turns * repetitions - return[ - [1.0 * element / turns for element in row] - for row in cooperation] - - -def vengeful_cooperation(cooperation): - """ - The vengeful cooperation matrix derived from the cooperation matrix. - - Parameters - ---------- - cooperation : list - A cooperation matrix (C) - - Returns - ------- - list - A matrix (D) such that: - - Dij = 2(Cij -0.5) - """ - return [[2 * (element - 0.5) for element in row] for row in cooperation] - - -def cooperating_rating(cooperation, nplayers, turns, repetitions): - """ - A list of cooperation ratings for each player - - Parameters - ---------- - cooperation : list - The cooperation matrix - nplayers : integer - The number of players in the tournament. - turns : integer - The number of turns in each round robin. - repetitions : integer - The number of repetitions in the tournament. - - Returns - ------- - list - a list of cooperation rates ordered by player index - """ - total_turns = turns * repetitions * nplayers - return [1.0 * sum(row) / total_turns for row in cooperation] - - -def null_matrix(nplayers): - """ - A null n by n matrix for n players - - Parameters - ---------- - nplayers : integer - The number of players in the tournament. - Returns - ------- - list - A null n by n matrix where n is the number of players. - """ - plist = list(range(nplayers)) - return [[0 for j in plist] for i in plist] - - -def good_partner_matrix(results, nplayers, repetitions): - """ - An n by n matrix of good partner ratings for n players - - Parameters - ---------- - results : list - A cooperation results matrix of the form: - - [ - [[a, j], [b, k], [c, l]], - [[d, m], [e, n], [f, o]], - [[g, p], [h, q], [i, r]], - ] - - i.e. one row per player, containing one element per opponent (in - order of player index) which lists cooperation values for each - repetition. - - nplayers : integer - The number of players in the tournament. - repetitions : integer - The number of repetitions in the tournament. - - Returns - ------- - list - The good partner matrix (P) of the form: - - [ - [0, 0 + (1 if b >= d) + (1 if k >= m), 0 + (1 if c >= g) + (1 if l >= p) ], - [0 + (1 if e >= g) + (1 if n >= p), 0, 0 + (1 if f >= h) + (1 if o >= q)], - [0 + (1 if g >= c) + (1 if p >= l), 0 + (1 if h >= f) + (1 if q >= o), 0] - ] - - i.e. an n by n matrix where n is the number of players. Each row (i) - and column (j) represents an individual player and the the value Pij - is the sum of the number of repetitions where player i cooperated as - often or more than opponent j. - """ - matrix = null_matrix(nplayers) - for r in range(repetitions): - for i in range(nplayers): - for j in range(nplayers): - if i != j and results[i][j][r] >= results[j][i][r]: - matrix[i][j] += 1 - return matrix - - -def n_interactions(nplayers, repetitions): - """ - The number of interactions between n players - - Parameters - ---------- - nplayers : integer - The number of players in the tournament. - repetitions : integer - The number of repetitions in the tournament. - - Returns - ------- - integer - The number of interactions between players excluding self-interactions. - """ - return repetitions * (nplayers - 1) - - -def good_partner_rating(good_partner_matrix, nplayers, repetitions): - """ - A list of good partner ratings for n players in order of rating - - Parameters - ---------- - good_partner_matrix : list - The good partner matrix - nplayers : integer - The number of players in the tournament. - repetitions : integer - The number of repetitions in the tournament. - - Returns - ------- - list - A list of good partner ratings ordered by player index. - """ - return [1.0 * sum(row) / n_interactions(nplayers, repetitions) - for row in good_partner_matrix] - - -def eigenvector(cooperation_matrix): - """ - The principal eigenvector of the cooperation matrix - - Parameters - ---------- - cooperation_matrix : list - A cooperation matrix - - Returns - ------- - list - The principal eigenvector of the cooperation matrix. - """ - eigenvector, eigenvalue = eigen.principal_eigenvector( - cooperation_matrix, 1000, 1e-3 - ) - return eigenvector.tolist() diff --git a/axelrod/eigen.py b/axelrod/eigen.py index 24e8428af..f47c46ecb 100644 --- a/axelrod/eigen.py +++ b/axelrod/eigen.py @@ -44,7 +44,7 @@ def power_iteration(mat, initial): yield vec -def principal_eigenvector(mat, maximum_iterations=None, max_error=1e-8): +def principal_eigenvector(mat, maximum_iterations=1000, max_error=1e-3): """ Computes the (normalised) principal eigenvector of the given matrix. diff --git a/axelrod/match.py b/axelrod/match.py index bb0747350..c2998ddcc 100644 --- a/axelrod/match.py +++ b/axelrod/match.py @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- from .game import Game +from axelrod import Actions + +C, D = Actions.C, Actions.D def sparkline(actions, c_symbol=u'█', d_symbol=u' '): @@ -26,8 +29,7 @@ def __init__(self, players, turns, deterministic_cache=None, The probability that a player's intended action should be flipped """ self.result = [] - self.player1 = players[0] - self.player2 = players[1] + self.players = list(players) self._classes = (players[0].__class__, players[1].__class__) self._turns = turns if deterministic_cache is None: @@ -45,8 +47,8 @@ def _stochastic(self): """ return ( self._noise or - self.player1.classifier['stochastic'] or - self.player2.classifier['stochastic']) + any(p.classifier['stochastic'] for p in self.players) + ) @property def _cache_update_required(self): @@ -56,8 +58,8 @@ def _cache_update_required(self): return ( not self._noise and self._cache_mutable and not ( - self.player1.classifier['stochastic'] or - self.player2.classifier['stochastic']) + any(p.classifier['stochastic'] for p in self.players) + ) ) def play(self): @@ -80,12 +82,13 @@ def play(self): """ if (self._stochastic or self._classes not in self._cache): turn = 0 - self.player1.reset() - self.player2.reset() + for p in self.players: + p.reset() while turn < self._turns: turn += 1 - self.player1.play(self.player2, self._noise) - result = list(zip(self.player1.history, self.player2.history)) + self.players[0].play(self.players[1], self._noise) + result = list( + zip(self.players[0].history, self.players[1].history)) if self._cache_update_required: self._cache[self._classes] = result @@ -102,8 +105,67 @@ def scores(self, game=None): scores = [game.score(plays) for plays in self.result] return scores + def final_score(self, game=None): + """Returns the final score for a Match""" + scores = self.scores(game) + + if len(scores) == 0: + return None + + final_score = tuple(sum([score[playeri] for score in scores]) + for playeri in [0, 1]) + return final_score + + def final_score_per_turn(self, game=None): + """Returns the mean score per round for a Match""" + scores = self.scores(game) + + if len(scores) == 0: + return None + + final_score_per_turn = tuple( + sum([score[playeri] for score in scores]) / (float(self._turns)) + for playeri in [0, 1]) + return final_score_per_turn + + def winner(self, game=None): + """Returns the winner of the Match""" + scores = self.final_score(game) + + if scores is not None: + if scores[0] == scores[1]: + return False # No winner + return sorted(self.players, + key=lambda x: scores[self.players.index(x)])[-1] + return None + + def cooperation(self): + """Returns the count of cooperations by each player""" + + if len(self.result) == 0: + return None + + cooperation = tuple(sum([play[playeri] == C for play in self.result]) + for playeri in [0, 1]) + return cooperation + + def normalised_cooperation(self): + """Returns the count of cooperations by each player per turn""" + cooperation = self.cooperation() + + if len(self.result) == 0: + return None + + normalised_cooperation = tuple( + [c / float(self._turns) for c in cooperation]) + + return normalised_cooperation + def sparklines(self, c_symbol=u'█', d_symbol=u' '): return ( - sparkline(self.player1.history, c_symbol, d_symbol) + + sparkline(self.players[0].history, c_symbol, d_symbol) + u'\n' + - sparkline(self.player2.history, c_symbol, d_symbol)) + sparkline(self.players[1].history, c_symbol, d_symbol)) + + def __len__(self): + return self._turns diff --git a/axelrod/payoff.py b/axelrod/payoff.py deleted file mode 100644 index 762f6db08..000000000 --- a/axelrod/payoff.py +++ /dev/null @@ -1,467 +0,0 @@ -import math -from numpy import median, mean -from axelrod import Actions - -C, D = Actions.C, Actions.D - - -def player_count(interactions): - """ - The number of players derived from a dictionary of interactions - - Parameters - ---------- - interactions : dictionary - A dictionary of the form: - - e.g. for a round robin between Cooperator, Defector and Alternator - with 2 turns per round: - { - (0, 0): [(C, C), (C, C)]. - (0, 1): [(C, D), (C, D)], - (0, 2): [(C, C), (C, D)], - (1, 1): [(D, D), (D, D)], - (1, 2): [(D, C), (D, D)], - (2, 2): [(C, C), (D, D)] - } - - i.e. the key is a pair of player index numbers and the value, a list of - plays. The list contains one pair per turn in the round robin. - The dictionary contains one entry for each combination of players. - - Returns - ------- - nplayers : integer - The number of players in the round robin - - The number of ways (c) to select groups of r members from a set of n - members is given by: - - c = n! / r!(n - r)! - - In this case, we are selecting pairs of players (p) and thus r = 2, - giving: - - p = n(n-1) / 2 or p = (n^2 - n) / 2 - - However, we also have the case where each player plays itself gving: - - p = (n^2 + n) / 2 - - Using the quadratic equation to rearrange for n gives: - - n = (-1 +- sqrt(1 + 8p)) / 2 - - Taking only the real roots allows us to derive the number of players - given the number of pairs: - - n = (sqrt(8p + 1) -1) / 2 - """ - return int((math.sqrt(len(interactions) * 8 + 1) - 1) / 2) - - -def payoff_matrix(interactions, game): - """ - The payoff matrix from a single round robin. - - Parameters - ---------- - interactions : dictionary - A dictionary of the form: - - e.g. for a round robin between Cooperator, Defector and Alternator - with 2 turns per round: - { - (0, 0): [(C, C), (C, C)]. - (0, 1): [(C, D), (C, D)], - (0, 2): [(C, C), (C, D)], - (1, 1): [(D, D), (D, D)], - (1, 2): [(D, C), (D, D)], - (2, 2): [(C, C), (D, D)] - } - - i.e. the key is a pair of player index numbers and the value, a list of - plays. The list contains one pair per turn in the round robin. - The dictionary contains one entry for each combination of players. - - nplayers : integer - The number of players in the round robin - game : axelrod.Game - The game object to score the tournament. - - Returns - ------- - list - A matrix (P) of the form: - - [ - [a, b, c], - [d, e, f], - [g, h, i] - ] - - i.e. an n by n matrix where n is the number of players. Each row (i) - and column (j) represents an individual player and the the value Pij - is the payoff value for player (i) versus player (j). - """ - nplayers = player_count(interactions) - payoffs = [[0 for i in range(nplayers)] for j in range(nplayers)] - for players, actions in interactions.items(): - payoff = interaction_payoff(actions, game) - payoffs[players[0]][players[1]] += payoff[0] - if players[0] != players[1]: - payoffs[players[1]][players[0]] += payoff[1] - return payoffs - - -def interaction_payoff(actions, game): - """ - The payoff values for a single interaction of n turns between two players. - - Parameters - ---------- - actions : list - A list of tuples of the form: - - [(C, C), (C, C)], [(C, D), (C, D)] - - game : axelrod.Game - The game object to score the actions. - - Returns - ------- - tuple - A pair of payoffs for each of the two players in the interaction. - - i.e. for the actions list quoted above, the resulting payoff returned - would be: - (6, 16) - """ - player1_payoff, player2_payoff = 0, 0 - for turn in actions: - score = game.score(turn) - player1_payoff += score[0] - player2_payoff += score[1] - return (player1_payoff, player2_payoff) - - -def scores(payoff): - """ - The scores matrix excluding self-interactions - - Parameters - ---------- - payoff : list - A matrix of the form: - - [ - [[a, j], [b, k], [c, l]], - [[d, m], [e, n], [f, o]], - [[g, p], [h, q], [i, r]], - ] - - i.e. one row per player, containing one element per opponent (in - order of player index) which lists payoffs for each repetition. - - Returns - ------- - list - A scores matrix of the form: - - [ - [a + b + c, j + k + l], - [d + e + f, m + n+ o], - [h + h + i, p + q + r], - ] - - i.e. one row per player which lists the total score for each - repetition. - - In Axelrod's original tournament, there were no self-interactions - (e.g. player 1 versus player 1) and so these are also excluded from the - scores here by the condition on ip and ires. - """ - nplayers = len(payoff) - repetitions = len(payoff[0][0]) - scores = [] - for ires, res in enumerate(payoff): - scores.append([]) - for irep in range(repetitions): - scores[-1].append(0) - for ip in range(nplayers): - if ip != ires: - scores[-1][-1] += res[ip][irep] - return scores - - -def normalised_scores(scores, turns): - """ - The per-turn normalised scores matrix - - Parameters - ---------- - scores : list - A scores matrix (S) of the form returned by the scores function. - turns : integer - The number of turns in each round robin. - - Returns - ------- - list - A normalised scores matrix (N) such that: - - N = S / t - - where t is the total number of turns played per repetition for a given - player excluding self-interactions. - """ - nplayers = len(scores) - normalisation = turns * (nplayers - 1) - return [ - [1.0 * s / normalisation for s in r] for r in scores] - - -def ranking(scores): - """ - Player index numbers listed by order of median score - - Parameters - ---------- - scores : list - A scores matrix (S) of the form returned by the scores function. - - Returns - ------- - list - A list of players (their index within the players list rather than - a player instance) ordered by median score - """ - nplayers = len(scores) - ranking = sorted( - range(nplayers), - key=lambda i: -median(scores[i])) - return ranking - - -def ranked_names(players, ranking): - """ - Player names listed by their ranked order - - Parameters - ---------- - players : list - The list of players in the tournament - ranking : list - A list of player index numbers - as returned by the ranking function - - Returns - ------- - list - A list of player names sorted by their ranked order. - """ - ranked_names = [str(players[i]) for i in ranking] - return ranked_names - - -def normalised_payoff(payoff_matrix, turns): - """ - The per-turn averaged payoff matrix and standard deviations - - Parameters - ---------- - payoff : list - A matrix of the form: - - [ - [[a, j], [b, k], [c, l]], - [[d, m], [e, n], [f, o]], - [[g, p], [h, q], [i, r]], - ] - - i.e. one row per player, containing one element per opponent (in - order of player index) which lists payoffs for each repetition. - - turns : integer - The number of turns in each round robin. - - Returns - ------- - list - A per-turn averaged payoff matrix and its standard deviations. - """ - repetitions = len(payoff_matrix[0][0]) - averages = [] - stddevs = [] - for res in payoff_matrix: - averages.append([]) - stddevs.append([]) - for s in res: - perturn = [1.0 * rep / turns for rep in s] - avg = sum(perturn) / repetitions - dev = math.sqrt( - sum([(avg - pt)**2 for pt in perturn]) / repetitions) - averages[-1].append(avg) - stddevs[-1].append(dev) - return averages, stddevs - - -def winning_player(players, payoffs): - """ - The index of a winning player from a pair of payoff values - - Parameters - ---------- - players : tuple - A pair of player indexes - payoffs : tuple - A pair of payoffs for the two players - - Returns - ------- - integer - The index of the winning player or None if a draw - """ - if payoffs[0] == payoffs[1]: - return None - else: - winning_payoff = max(payoffs) - winning_payoff_index = payoffs.index(winning_payoff) - winner = players[winning_payoff_index] - return winner - - -def wins(payoff): - """ - An n by n matrix of win counts for n players - - Parameters - ---------- - payoff : list - A matrix of the form: - - [ - [[a, j], [b, k], [c, l]], - [[d, m], [e, n], [f, o]], - [[g, p], [h, q], [i, r]], - ] - - i.e. one row per player, containing one element per opponent (in - order of player index) which lists payoffs for each repetition. - - Returns - ------- - list - A wins matrix of the form: - - [ - [player1 wins in repetition1, player1 wins in repetition2], - [player2 wins in repetition1, player2 wins in repetition2], - [player3 wins in repetition1, player3 wins in repetition2], - ] - - i.e. one row per player which lists the total wins for that player - in each repetition. - """ - nplayers = len(payoff) - repetitions = len(payoff[0][0]) - wins = [ - [0 for r in range(repetitions)] for p in range(nplayers)] - for player in range(nplayers): - for opponent in range(player + 1): # Ensures we don't count wins twice - players = (player, opponent) - for repetition in range(repetitions): - payoffs = ( - payoff[player][opponent][repetition], - payoff[opponent][player][repetition]) - winner = winning_player(players, payoffs) - if winner is not None: - wins[winner][repetition] += 1 - return wins - - -def payoff_diffs_means(payoff, turns): - """ - An n by n matrix of mean payoff differences for n players - - Parameters - ---------- - payoff : list - A matrix of the form: - - [ - [[a, j], [b, k], [c, l]], - [[d, m], [e, n], [f, o]], - [[g, p], [h, q], [i, r]], - ] - - i.e. one row per player, containing one element per opponent (in - order of player index) which lists payoffs for each repetition. - - turns : integer - The number of turns in each round robin. - - Returns - ------- - list (of lists) - A matrix of mean payoff differences of the form with i, j entry: - mean([player_i payoff - player_j payoff in repetition1, - player_i payoff - player_j payoff in repetition2, - ...]) - - normalized by the number of turns. I.e. the nplayers x nplayers - matrix of mean payoff differences between each player and opponent. - """ - nplayers = len(payoff) - repetitions = len(payoff[0][0]) - diffs_matrix = [[0] * nplayers for _ in range(nplayers)] - for player in range(nplayers): - for opponent in range(nplayers): - diffs = [] - for repetition in range(repetitions): - diff = (payoff[player][opponent][repetition] - payoff[opponent][player][repetition]) / float(turns) - diffs.append(diff) - diffs_matrix[player][opponent] = mean(diffs) - - return diffs_matrix - - -def score_diffs(payoff, turns): - """ - An n by n matrix of per-turn normalised payoff differences for n players - - Parameters - ---------- - payoff : list - A matrix of the form: - - [ - [[a, j], [b, k], [c, l]], - [[d, m], [e, n], [f, o]], - [[g, p], [h, q], [i, r]], - ] - - i.e. one row per player, containing one element per opponent (in - order of player index) which lists payoffs for each repetition. - - turns : integer - The number of turns in each round robin. - - Returns - ------- - list (of lists of lists) - A matrix of payoff differences of the form with i, j entry: - [player_i payoff - player_j payoff for each j and each repetition] - where the payoffs have been normalized by the number of turns and summed - over the repititions. - """ - nplayers = len(payoff) - repetitions = len(payoff[0][0]) - diffs = [ - [] for p in range(nplayers)] - for player in range(nplayers): - for opponent in range(nplayers): - for repetition in range(repetitions): - diff = (payoff[player][opponent][repetition] - payoff[opponent][player][repetition]) / float(turns) - diffs[player].append(diff) - - return diffs diff --git a/axelrod/plot.py b/axelrod/plot.py index c3cb14a94..6859a3b0a 100644 --- a/axelrod/plot.py +++ b/axelrod/plot.py @@ -1,6 +1,5 @@ - from numpy.linalg import LinAlgError -from numpy import arange, mean, median +from numpy import arange, median from warnings import warn matplotlib_installed = True @@ -28,7 +27,7 @@ def __init__(self, result_set): self.result_set = result_set self.matplotlib_installed = matplotlib_installed - ## Abstract Box and Violin plots + # Abstract Box and Violin plots def _boxplot(self, data, names, title=None): """For making boxplots.""" @@ -64,11 +63,13 @@ def _violinplot(self, data, names, title=None): plt.title(title) return figure - ## Box and Violin plots for mean score, score diferrences, and wins + # Box and Violin plots for mean score, score differences, wins, and match + # lengths @property def _boxplot_dataset(self): - return [self.result_set.normalised_scores[ir] for ir in self.result_set.ranking] + return [self.result_set.normalised_scores[ir] + for ir in self.result_set.ranking] @property def _boxplot_xticks_locations(self): @@ -78,19 +79,10 @@ def _boxplot_xticks_locations(self): def _boxplot_xticks_labels(self): return [str(n) for n in self.result_set.ranked_names] - @property - def _boxplot_title(self): - return ("Mean score per stage game over {} " - "turns repeated {} times ({} strategies)").format( - self.result_set.turns, - self.result_set.repetitions, - len(self.result_set.ranking)) - - def boxplot(self): + def boxplot(self, title=None): """For the specific mean score boxplot.""" data = self._boxplot_dataset names = self._boxplot_xticks_labels - title = self._boxplot_title try: figure = self._violinplot(data, names, title=title) except LinAlgError: @@ -107,27 +99,19 @@ def _winplot_dataset(self): wins = self.result_set.wins players = self.result_set.players medians = map(median, wins) - medians = sorted([(m, i) for (i, m) in enumerate(medians)], reverse=True) + medians = sorted( + [(m, i) for (i, m) in enumerate(medians)], reverse=True) # Reorder and grab names wins = [wins[x[-1]] for x in medians] ranked_names = [str(players[x[-1]]) for x in medians] return wins, ranked_names - @property - def _winplot_title(self): - return ("Distributions of wins:" - " {} turns repeated {} times ({} strategies)").format( - self.result_set.turns, - self.result_set.repetitions, - len(self.result_set.ranking)) - - def winplot(self): + def winplot(self, title=None): """Plots the distributions for the number of wins for each strategy.""" if not self.matplotlib_installed: return None data, names = self._winplot_dataset - title = self._winplot_title try: figure = self._violinplot(data, names, title) except LinAlgError: @@ -141,25 +125,17 @@ def winplot(self): plt.ylim(-0.5, 0.5 + maximum) return figure - @property - def _sdv_plot_title(self): - return ("Distributions of payoff differences per stage game over {} " - "turns repeated {} times ({} strategies)").format( - self.result_set.turns, - self.result_set.repetitions, - len(self.result_set.ranking)) - @property def _sd_ordering(self): return self.result_set.ranking - ## Sort by median then max - #from operator import itemgetter - #diffs = self.result_set.score_diffs - #to_sort = [(median(d), max(d), i) for (i, d) in enumerate(diffs)] - #to_sort.sort(reverse=True, key=itemgetter(0, 1)) - #ordering = [x[-1] for x in to_sort] - #return ordering + # Sort by median then max + # from operator import itemgetter + # diffs = self.result_set.score_diffs + # to_sort = [(median(d), max(d), i) for (i, d) in enumerate(diffs)] + # to_sort.sort(reverse=True, key=itemgetter(0, 1)) + # ordering = [x[-1] for x in to_sort] + # return ordering @property def _sdv_plot_dataset(self): @@ -171,20 +147,41 @@ def _sdv_plot_dataset(self): ranked_names = [str(players[i]) for i in ordering] return diffs, ranked_names - def sdvplot(self): + def sdvplot(self, title=None): """Score difference violinplots to visualize the distributions of how players attain their payoffs.""" diffs, ranked_names = self._sdv_plot_dataset - title = self._sdv_plot_title figure = self._violinplot(diffs, ranked_names, title) return figure - ## Payoff heatmaps + @property + def _lengthplot_dataset(self): + match_lengths = self.result_set.match_lengths + return [[length for rep in match_lengths + for length in rep[playeri]] for playeri in + self.result_set.ranking] + + def lengthplot(self, title=None): + """For the specific match length boxplot.""" + data = self._lengthplot_dataset + names = self._boxplot_xticks_labels + try: + figure = self._violinplot(data, names, title=title) + except LinAlgError: + # Matplotlib doesn't handle single point distributions well + # in violin plots. Should be fixed in next release: + # https://github.com/matplotlib/matplotlib/pull/4816 + # Fall back to boxplot + figure = self._boxplot(data, names, title=title) + return figure + + # Payoff heatmaps @property def _payoff_dataset(self): - return [[self.result_set.payoff_matrix[r1][r2] - for r2 in self.result_set.ranking] + pm = self.result_set.payoff_matrix + return [[pm[r1][r2] + for r2 in self.result_set.ranking] for r1 in self.result_set.ranking] @property @@ -238,7 +235,7 @@ def payoff(self): names = self.result_set.ranked_names return self._payoff_heatmap(data, names) - ## Ecological Plot + # Ecological Plot def stackplot(self, eco): diff --git a/axelrod/result_set.py b/axelrod/result_set.py index d9c6e7eb8..aa39bdda3 100644 --- a/axelrod/result_set.py +++ b/axelrod/result_set.py @@ -1,6 +1,7 @@ import csv +from . import eigen -from axelrod import payoff as ap, cooperation as ac +from numpy import mean, median, std try: # Python 2 @@ -13,132 +14,621 @@ class ResultSet(object): """A class to hold the results of a tournament.""" - def __init__(self, players, turns, repetitions, outcome, - with_morality=True): + def __init__(self, players, matches, with_morality=True): """ - Args: - players (list): a list of player objects. - turns (int): the number of turns per interaction. - repetitions (int): the number of time the round robin was repeated. - outcome (dict): returned from the Tournament class and containing - various sets of results for processing by this class. - with_morality (bool): a flag to determine whether morality metrics - should be calculated. + Parameters + ---------- + players : list + a list of player objects. + matches : list + a list of dictionaries mapping tuples of player indices to + completed matches (1 for each repetition) + with_morality : bool + a flag to determine whether morality metrics should be + calculated. """ self.players = players self.nplayers = len(players) - self.turns = turns - self.repetitions = repetitions - self.outcome = outcome - self.results = self._results(outcome) - self.scores = None - self.normalised_scores = None - self.ranking = None - self.ranked_names = None - self.payoff_matrix = None - self.wins = None - self.cooperation = None - self.normalised_cooperation = None - self.vengeful_cooperation = None - self.cooperating_rating = None - self.good_partner_matrix = None - self.good_partner_rating = None - self.eigenjesus_rating = None - self.eigenmoses_rating = None - - if 'payoff' in self.results: - payoff = self.results['payoff'] - self.scores = ap.scores(payoff) - self.normalised_scores = ap.normalised_scores(self.scores, turns) - self.ranking = ap.ranking(self.scores) - self.ranked_names = ap.ranked_names(players, self.ranking) - self.payoff_matrix, self.payoff_stddevs = (ap.normalised_payoff( - payoff, turns)) - self.wins = ap.wins(payoff) - self.payoff_diffs_means = ap.payoff_diffs_means(payoff, turns) - self.score_diffs = ap.score_diffs(payoff, turns) - - if 'cooperation' in self.results and with_morality: - self.cooperation = ac.cooperation(self.results['cooperation']) - self.normalised_cooperation = ac.normalised_cooperation( - self.cooperation, turns, repetitions) - self.vengeful_cooperation = ac.vengeful_cooperation( - self.normalised_cooperation) - self.cooperating_rating = ac.cooperating_rating( - self.cooperation, len(players), turns, repetitions) - self.good_partner_matrix = ac.good_partner_matrix( - self.results['cooperation'], len(players), repetitions) - self.good_partner_rating = ac.good_partner_rating( - self.good_partner_matrix, len(players), repetitions) - self.eigenjesus_rating = ac.eigenvector(self.normalised_cooperation) - self.eigenmoses_rating = ac.eigenvector(self.vengeful_cooperation) + self.matches = matches + self.nrepetitions = len(matches) + + # Calculate all attributes: + self.wins = self.build_wins() + self.match_lengths = self.build_match_lengths() + + self.scores = self.build_scores() + self.normalised_scores = self.build_normalised_scores() + self.ranking = self.build_ranking() + self.ranked_names = self.build_ranked_names() + self.payoffs = self.build_payoffs() + self.payoff_matrix = self.build_payoff_matrix() + self.payoff_stddevs = self.build_payoff_stddevs() + self.score_diffs = self.build_score_diffs() + self.payoff_diffs_means = self.build_payoff_diffs_means() + + if with_morality: + self.cooperation = self.build_cooperation() + self.normalised_cooperation = self.build_normalised_cooperation() + self.vengeful_cooperation = self.build_vengeful_cooperation() + self.cooperating_rating = self.build_cooperating_rating() + self.good_partner_matrix = self.build_good_partner_matrix() + self.good_partner_rating = self.build_good_partner_rating() + self.eigenmoses_rating = self.build_eigenmoses_rating() + self.eigenjesus_rating = self.build_eigenjesus_rating() @property def _null_results_matrix(self): """ Returns: + -------- A null matrix (i.e. fully populated with zero values) using lists of the form required for the results dictionary. - i.e. one row per player, containing one element per opponent (in order - of player index) which lists values for each repetition. + i.e. one row per player, containing one element per opponent (in + order of player index) which lists values for each repetition. """ plist = list(range(self.nplayers)) - replist = list(range(self.repetitions)) - return [[[0 for r in replist] for j in plist] for i in plist] + replist = list(range(self.nrepetitions)) + return [[[0 for j in plist] for i in plist] for r in replist] - @property - def _null_matrix(self): + def build_match_lengths(self): + """ + Returns: + -------- + The match lengths. List of the form: + + [ML1, ML2, ML3..., MLn] + + Where n is the number of repetitions and MLi is a list of the form: + + [Pli1, PLi2, Pli3, ..., Plim] + + Where m is the number of players and Plij is of the form: + + [aij1, aij2, aij3, ..., aijk] + + Where k is the number of players and aijk is the length of the match + between player j and k in repetition i. + """ + match_lengths = self._null_results_matrix + + for rep in range(self.nrepetitions): + + for player_pair_index, match in self.matches[rep].items(): + i, j = player_pair_index + match_lengths[rep][i][j] = len(match) + + if i != j: # Match lengths are symmetric + match_lengths[rep][j][i] = len(match) + + return match_lengths + + def build_scores(self): """ Returns: - A null n by n matrix where n is the number of players. + -------- + The total scores per player for each repetition lengths. + List of the form: + + [ML1, ML2, ML3..., MLn] + + Where n is the number of players and MLi is a list of the form: + + [pi1, pi2, pi3, ..., pim] + + Where m is the number of repetitions and pij is the total score + obtained by each player in repetition j. + + In Axelrod's original tournament, there were no self-interactions + (e.g. player 1 versus player 1) and so these are also ignored. + """ + scores = [[0 for rep in range(self.nrepetitions)] for _ in + range(self.nplayers)] + + for rep, matches_dict in enumerate(self.matches): + for index_pair, match in matches_dict.items(): + if index_pair[0] != index_pair[1]: # Ignoring self interactions + for player in range(2): + player_index = index_pair[player] + player_score = match.final_score()[player] + scores[player_index][rep] += player_score + + return scores + + def build_ranked_names(self): + """ + Returns: + -------- + Returns the ranked names. A list of names as calculated by + self.ranking. + """ + return [str(self.players[i]) for i in self.ranking] + + def build_wins(self): + """ + Returns: + -------- + + The total wins per player for each repetition lengths. + List of the form: + + [ML1, ML2, ML3..., MLn] + + Where n is the number of players and MLi is a list of the form: + + [pi1, pi2, pi3, ..., pim] + + Where m is the number of repetitions and pij is the total wins + obtained by each player in repetition j. + + In Axelrod's original tournament, there were no self-interactions + (e.g. player 1 versus player 1) and so these are also ignored. + """ + wins = [[0 for rep in range(self.nrepetitions)] for _ in + range(self.nplayers)] + + for rep, matches_dict in enumerate(self.matches): + for index_pair, match in matches_dict.items(): + if index_pair[0] != index_pair[1]: # Ignore self interactions + for player in range(2): + player_index = index_pair[player] + + if match.players[player] == match.winner(): + wins[player_index][rep] += 1 + + return wins + + def build_normalised_scores(self): + """ + Returns: + -------- + + The total mean scores per turn per layer for each repetition + lengths. List of the form: + + [ML1, ML2, ML3..., MLn] + + Where n is the number of players and MLi is a list of the form: + + [pi1, pi2, pi3, ..., pim] + + Where m is the number of repetitions and pij is the mean scores per + turn obtained by each player in repetition j. + + In Axelrod's original tournament, there were no self-interactions + (e.g. player 1 versus player 1) and so these are also ignored. + """ + normalised_scores = [ + [[] for rep in range(self.nrepetitions)] for _ in + range(self.nplayers)] + + # Getting list of all per turn scores for each player for each rep + for rep, matches_dict in enumerate(self.matches): + for index_pair, match in matches_dict.items(): + if index_pair[0] != index_pair[1]: # Ignore self interactions + for player in range(2): + player_index = index_pair[player] + score_per_turn = match.final_score_per_turn()[player] + normalised_scores[player_index][rep].append(score_per_turn) + + # Obtaining mean scores and overwriting corresponding entry in + # normalised scores + for i, rep in enumerate(normalised_scores): + for j, player_scores in enumerate(rep): + normalised_scores[i][j] = mean(player_scores) + + return normalised_scores + + def build_ranking(self): + """ + Returns: + -------- + + The ranking. List of the form: + + [R1, R2, R3..., Rn] + + Where n is the number of players and Rj is the rank of the jth player + (based on median normalised score). + """ + return sorted(range(self.nplayers), + key=lambda i: -median(self.normalised_scores[i])) + + def build_payoffs(self): + """ + Returns: + -------- + + The list of per turn payoffs. + List of the form: + + [ML1, ML2, ML3..., MLn] + + Where n is the number of players and MLi is a list of the form: + + [pi1, pi2, pi3, ..., pim] + + Where m is the number of players and pij is a list of the form: + + [uij1, uij2, ..., uijk] + + Where k is the number of repetitions and uijk is the list of utilities + obtained by player i against player j in each repetition. """ plist = list(range(self.nplayers)) - return [[0 for j in plist] for i in plist] + payoffs = [[[] for j in plist] for i in plist] + + for i in plist: + for j in plist: + utilities = [] + for rep in self.matches: + + if (i, j) in rep: + match = rep[(i, j)] + utilities.append(match.final_score_per_turn()[0]) + if (j, i) in rep: + match = rep[(j, i)] + utilities.append(match.final_score_per_turn()[1]) - def _results(self, outcome): + payoffs[i][j] = utilities + return payoffs + + def build_payoff_matrix(self): """ - Args: - outcome(dict): the outcome dictionary, in which the values are - lists of the form: + Returns: + -------- + The mean of per turn payoffs. + List of the form: + + [ML1, ML2, ML3..., MLn] + + Where n is the number of players and MLi is a list of the form: - [ - [[a, b, c], [d, e, f], [g, h, i]], - [[j, k, l], [m, n, o], [p, q, r]], - ] + [pi1, pi2, pi3, ..., pim] - i.e. one row per repetition, containing one element per player, - which lists values for each opponent in order of player index. + Where m is the number of players and pij is a list of the form: + [uij1, uij2, ..., uijk] + + Where k is the number of repetitions and u is the mean utility (over + all repetitions) obtained by player i against player j. + """ + plist = list(range(self.nplayers)) + payoff_matrix = [[[0] for j in plist] for i in plist] + + for i in plist: + for j in plist: + utilities = self.payoffs[i][j] + + if utilities: + payoff_matrix[i][j] = mean(utilities) + else: + payoff_matrix[i][j] = 0 + + return payoff_matrix + + def build_payoff_stddevs(self): + """ Returns: - A results dictionary, in which the values are lists of - the form: + -------- + + The mean of per turn payoffs. + List of the form: - [ - [[a, j], [b, k], [c, l]], - [[d, m], [e, n], [f, o]], - [[g, p], [h, q], [i, r]], - ] + [ML1, ML2, ML3..., MLn] - i.e. one row per player, containing one element per opponent (in order - of player index) which lists values for each repetition. + Where n is the number of players and MLi is a list of the form: + + [pi1, pi2, pi3, ..., pim] + + Where m is the number of players and pij is a list of the form: + + [uij1, uij2, ..., uijk] + + Where k is the number of repetitions and u is the standard + deviation of the utility (over all repetitions) obtained by player + i against player j. + """ + plist = list(range(self.nplayers)) + payoff_stddevs = [[[0] for j in plist] for i in plist] + + for i in plist: + for j in plist: + utilities = self.payoffs[i][j] + + if utilities: + payoff_stddevs[i][j] = std(utilities) + else: + payoff_stddevs[i][j] = 0 + + return payoff_stddevs + + def build_score_diffs(self): """ - results = {} - for result_type, result_list in outcome.items(): - matrix = self._null_results_matrix - for index, result_matrix in enumerate(result_list): - for i in range(len(self.players)): - for j in range(len(self.players)): - matrix[i][j][index] = result_matrix[i][j] - results[result_type] = matrix - return results + Returns: + -------- + + Returns the score differences between players. + List of the form: + + [ML1, ML2, ML3..., MLn] + + Where n is the number of players and MLi is a list of the form: + + [pi1, pi2, pi3, ..., pim] + + Where m is the number of players and pij is a list of the form: + + [uij1, uij2, ..., uijk] + + Where k is the number of repetitions and uijm is the difference of the + scores per turn between player i and j in repetition m. + """ + plist = list(range(self.nplayers)) + score_diffs = [[[0] * self.nrepetitions for j in plist] for i in plist] + + for i in plist: + for j in plist: + for r, rep in enumerate(self.matches): + if (i, j) in rep: + scores = rep[(i, j)].final_score_per_turn() + diff = (scores[0] - scores[1]) + score_diffs[i][j][r] = diff + if (j, i) in rep: + scores = rep[(j, i)].final_score_per_turn() + diff = (scores[1] - scores[0]) + score_diffs[i][j][r] = diff + return score_diffs + + def build_payoff_diffs_means(self): + """ + Returns: + -------- + + The score differences between players. + List of the form: + + [ML1, ML2, ML3..., MLn] + + Where n is the number of players and MLi is a list of the form: + + [pi1, pi2, pi3, ..., pim] + + Where pij is the mean difference of the + scores per turn between player i and j in repetition m. + """ + plist = list(range(self.nplayers)) + payoff_diffs_means = [[0 for j in plist] for i in plist] + + for i in plist: + for j in plist: + diffs = [] + for rep in self.matches: + if (i, j) in rep: + scores = rep[(i, j)].final_score_per_turn() + diffs.append(scores[0] - scores[1]) + if (j, i) in rep: + scores = rep[(j, i)].final_score_per_turn() + diffs.append(scores[1] - scores[0]) + if diffs: + payoff_diffs_means[i][j] = mean(diffs) + else: + payoff_diffs_means[i][j] = 0 + return payoff_diffs_means + + def build_cooperation(self): + """ + Returns: + -------- + + The list of cooperation counts. + List of the form: + + [ML1, ML2, ML3..., MLn] + + Where n is the number of players and MLi is a list of the form: + + [pi1, pi2, pi3, ..., pim] + + Where pij is the total number of cooperations over all repetitions + played by player i against player j. + """ + plist = list(range(self.nplayers)) + cooperations = [[0 for j in plist] for i in plist] + + for i in plist: + for j in plist: + if i != j: + for rep in self.matches: + coop_count = 0 + + if (i, j) in rep: + match = rep[(i, j)] + coop_count = match.cooperation()[0] + if (j, i) in rep: + match = rep[(j, i)] + coop_count = match.cooperation()[1] + + cooperations[i][j] += coop_count + return cooperations + + def build_normalised_cooperation(self): + """ + Returns: + -------- + + The list of per turn cooperation counts. + List of the form: + + [ML1, ML2, ML3..., MLn] + + Where n is the number of players and MLi is a list of the form: + + [pi1, pi2, pi3, ..., pin] + + Where pij is the mean number of + cooperations per turn played by player i against player j in each + repetition. + """ + plist = list(range(self.nplayers)) + normalised_cooperations = [[0 for j in plist] for i in plist] + + for i in plist: + for j in plist: + coop_counts = [] + for rep in self.matches: + + if (i, j) in rep: + match = rep[(i, j)] + coop_counts.append(match.normalised_cooperation()[0]) + + if (j, i) in rep: + match = rep[(j, i)] + coop_counts.append(match.normalised_cooperation()[1]) + + if ((i, j) not in rep) and ((j, i) not in rep): + coop_counts.append(0) + + # Mean over all reps: + normalised_cooperations[i][j] = mean(coop_counts) + return normalised_cooperations + + def build_vengeful_cooperation(self): + """ + Returns: + -------- + + The vengeful cooperation matrix derived from the + normalised cooperation matrix: + + Dij = 2(Cij - 0.5) + """ + return [[2 * (element - 0.5) for element in row] + for row in self.normalised_cooperation] + + def build_cooperating_rating(self): + """ + Returns: + -------- + + The list of cooperation counts + List of the form: + + [ML1, ML2, ML3..., MLn] + + Where n is the number of players and MLi is a list of the form: + + [pi1, pi2, pi3, ..., pim] + + Where pij is the total number of cooperations divided by the total + number of turns over all repetitions played by player i against + player j. + """ + + plist = list(range(self.nplayers)) + total_length_v_opponent = [zip(*[rep[playeri] for + rep in self.match_lengths]) + for playeri in plist] + lengths = [[sum(e) for j, e in enumerate(row) if i != j] for i, row in + enumerate(total_length_v_opponent)] + + # Max is to deal with edge cases of matches that have no turns + return [sum(cs) / max(1, float(sum(ls))) for cs, ls + in zip(self.cooperation, lengths)] + + def build_good_partner_matrix(self): + """ + Returns: + -------- + + An n by n matrix of good partner ratings for n players i.e. an n by + n matrix where n is the number of players. Each row (i) and column + (j) represents an individual player and the value Pij is the sum of + the number of repetitions where player i cooperated as often or + more than opponent j. + """ + + plist = list(range(self.nplayers)) + good_partner_matrix = [[0 for j in plist] for i in plist] + + for i in plist: + for j in plist: + if i != j: + for rep in self.matches: + + if (i, j) in rep: + match = rep[(i, j)] + coops = match.cooperation() + if coops[0] >= coops[1]: + good_partner_matrix[i][j] += 1 + + if (j, i) in rep: + match = rep[(j, i)] + coops = match.cooperation() + if coops[0] <= coops[1]: + good_partner_matrix[i][j] += 1 + + return good_partner_matrix + + def build_good_partner_rating(self): + """ + Returns: + -------- + + A list of good partner ratings ordered by player index. + """ + plist = list(range(self.nplayers)) + good_partner_rating = [] + + for playeri in plist: + total_interactions = 0 + for rep in self.matches: + total_interactions += len( + [pair for pair in rep.keys() + if playeri in pair and pair[0] != pair[1]]) + # Max is to deal with edge case of matchs with no turns + rating = sum(self.good_partner_matrix[playeri]) / max(1, float(total_interactions)) + good_partner_rating.append(rating) + + return good_partner_rating + + def build_eigenjesus_rating(self): + """ + Returns: + -------- + + The eigenjesus rating as defined in: + http://www.scottaaronson.com/morality.pdf + """ + eigenvector, eigenvalue = eigen.principal_eigenvector( + self.normalised_cooperation) + return eigenvector.tolist() + + def build_eigenmoses_rating(self): + """ + Returns: + -------- + + The eigenmoses rating as defined in: + http://www.scottaaronson.com/morality.pdf + """ + eigenvector, eigenvalue = eigen.principal_eigenvector( + self.vengeful_cooperation) + return eigenvector.tolist() def csv(self): + """ + Returns: + -------- + + The string of the total scores per player (columns) per repetition + (rows). + """ csv_string = StringIO() header = ",".join(self.ranked_names) + "\n" csv_string.write(header) writer = csv.writer(csv_string, lineterminator="\n") - for irep in range(self.repetitions): + for irep in range(self.nrepetitions): data = [self.normalised_scores[rank][irep] for rank in self.ranking] writer.writerow(list(map(str, data))) diff --git a/axelrod/tests/integration/test_tournament.py b/axelrod/tests/integration/test_tournament.py index 8b4cecf1d..f3e67d3d7 100644 --- a/axelrod/tests/integration/test_tournament.py +++ b/axelrod/tests/integration/test_tournament.py @@ -30,9 +30,9 @@ def test_full_tournament(self): """A test to check that tournament runs with all non cheating strategies.""" strategies = [strategy() for strategy in axelrod.ordinary_strategies] tournament = axelrod.Tournament(name='test', players=strategies, game=self.game, turns=20, repetitions=2) - output_of_tournament = tournament.play().results - self.assertEqual(type(output_of_tournament), dict) - self.assertEqual(len(output_of_tournament['payoff']), len(strategies)) + results = tournament.play() + self.assertIsInstance(results, axelrod.ResultSet) + #self.assertEqual(len(output_of_tournament['payoff']), len(strategies)) def test_serial_play(self): tournament = axelrod.Tournament( diff --git a/axelrod/tests/unit/test_cooperation.py b/axelrod/tests/unit/test_cooperation.py deleted file mode 100644 index 55b1531df..000000000 --- a/axelrod/tests/unit/test_cooperation.py +++ /dev/null @@ -1,135 +0,0 @@ -import unittest -from axelrod import Actions -import axelrod.cooperation as ac - -C, D = Actions.C, Actions.D - -class TestCooperation(unittest.TestCase): - - @classmethod - def setUpClass(cls): - - cls.interactions = { - (0, 0): [(C, C), (D, D), (C, C), (D, D), (C, C)], - (0, 1): [(C, C), (D, C), (C, D), (D, C), (C, D)], - (0, 2): [(C, C), (D, C), (C, D), (D, C), (C, D)], - (1, 1): [(C, C), (C, C), (C, C), (C, C), (C, C)], - (1, 2): [(C, D), (D, D), (D, C), (C, D), (D, C)], - (2, 2): [(D, C), (D, D), (D, C), (D, D), (D, D)] - - } - - cls.expected_cooperation_matrix = [ - [3, 3, 3], - [3, 5, 2], - [3, 2, 0] - ] - - cls.expected_cooperation_results = [ - [[3, 3], [3, 3], [3, 3]], - [[3, 3], [5, 5], [4, 2]], - [[4, 3], [4, 1], [1, 4]] - ] - - cls.expected_cooperation = [ - [6, 6, 6], - [6, 10, 6], - [7, 5, 5] - ] - - cls.expected_normalised_cooperation = [ - [0.6, 0.6, 0.6], - [0.6, 1.0, 0.6], - [0.7, 0.5, 0.5] - ] - - cls.expected_vengeful_cooperation = [ - [0.2, 0.2, 0.2], - [0.2, 1.0, 0.2], - [0.4, 0.0, 0.0]] - - cls.expected_cooperating_rating = [0.6, 0.73, 0.57] - - cls.expected_null_matrix = [ - [0, 0, 0], - [0, 0, 0], - [0, 0, 0] - ] - - cls.expected_good_partner_matrix = [ - [0, 2, 1], - [2, 0, 2], - [2, 1, 0] - ] - - cls.expected_good_partner_rating = [0.75, 1.0, 0.75] - - cls.expected_eigenvector = [0.54, 0.68, 0.5] - - def test_cooperation_matrix(self): - cooperation_matrix = ac.cooperation_matrix(self.interactions) - self.assertEqual(cooperation_matrix, self.expected_cooperation_matrix) - - def test_cooperation(self): - cooperation_matrix = ac.cooperation(self.expected_cooperation_results) - self.assertEqual(cooperation_matrix, self.expected_cooperation) - - def test_normalised_cooperation(self): - normalised_cooperation = ac.normalised_cooperation( - self.expected_cooperation, 5, 2 - ) - self.assertEqual( - normalised_cooperation, self.expected_normalised_cooperation - ) - - @staticmethod - def round_matrix(matrix, precision): - return [[round(x, precision) for x in row] for row in matrix] - - def test_vengeful_cooperation(self): - vengeful_cooperation = ac.vengeful_cooperation( - self.expected_normalised_cooperation - ) - self.assertEqual( - self.round_matrix(vengeful_cooperation, 2), - self.expected_vengeful_cooperation - ) - - @staticmethod - def round_rating(rating, precision): - return [round(x, precision) for x in rating] - - def test_cooperating_rating(self): - cooperating_rating = ac.cooperating_rating( - self.expected_cooperation, 3, 5, 2 - ) - self.assertEqual( - self.round_rating(cooperating_rating, 2), - self.expected_cooperating_rating - ) - - def test_null_matrix(self): - null_matrix = ac.null_matrix(3) - self.assertEqual(null_matrix, self.expected_null_matrix) - - def test_good_partner_matrix(self): - good_partner_matrix = ac.good_partner_matrix( - self.expected_cooperation_results, 3, 2 - ) - self.assertEqual(good_partner_matrix, self.expected_good_partner_matrix) - - def test_n_interactions(self): - n_interactions = ac.n_interactions(3, 2) - self.assertEqual(n_interactions, 4) - - def test_good_partner_rating(self): - good_partner_rating = ac.good_partner_rating( - self.expected_good_partner_matrix, 3, 2 - ) - self.assertEqual(good_partner_rating, self.expected_good_partner_rating) - - def test_eigenvector(self): - eigenvector = ac.eigenvector(self.expected_cooperation) - self.assertEqual( - self.round_rating(eigenvector, 2), self.expected_eigenvector - ) diff --git a/axelrod/tests/unit/test_eigen.py b/axelrod/tests/unit/test_eigen.py index 33ac94c4b..8d27673a6 100644 --- a/axelrod/tests/unit/test_eigen.py +++ b/axelrod/tests/unit/test_eigen.py @@ -31,7 +31,8 @@ def test_eigen_2(self): def test_eigen_3(self): # Test a 3x3 matrix mat = [[1, 2, 0], [-2, 1, 2], [1, 3, 1]] - evector, evalue = principal_eigenvector(mat) + 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([0.5, 0.5, 1])) @@ -39,7 +40,8 @@ def test_eigen_3(self): def test_eigen_4(self): # Test a 4x4 matrix mat = [[2, 0, 0, 0], [1, 2, 0, 0], [0, 1, 3, 0], [0, 0, 1, 3]] - evector, evalue = principal_eigenvector(mat, max_error=1e-10) + 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, normalise([0, 0, 0, 1]), decimal=4) diff --git a/axelrod/tests/unit/test_match.py b/axelrod/tests/unit/test_match.py index e64c7b840..cff0ab3d1 100644 --- a/axelrod/tests/unit/test_match.py +++ b/axelrod/tests/unit/test_match.py @@ -18,8 +18,7 @@ def test_init(self, turns): p1, p2 = axelrod.Cooperator(), axelrod.Cooperator() match = axelrod.Match((p1, p2), turns) self.assertEqual(match.result, []) - self.assertEqual(match.player1, p1) - self.assertEqual(match.player2, p2) + self.assertEqual(match.players, [p1, p2]) self.assertEqual( match._classes, (axelrod.Cooperator, axelrod.Cooperator)) self.assertEqual(match._turns, turns) @@ -27,6 +26,13 @@ def test_init(self, turns): self.assertEqual(match._cache_mutable, True) self.assertEqual(match._noise, 0) + @given(turns=integers(min_value=1, max_value=200)) + @example(turns=5) + def test_len(self, turns): + p1, p2 = axelrod.Cooperator(), axelrod.Cooperator() + match = axelrod.Match((p1, p2), turns) + self.assertEqual(len(match), turns) + @given(p=floats(min_value=0, max_value=1), rm=random_module()) def test_stochastic(self, p, rm): @@ -87,6 +93,91 @@ def test_scores(self): match.play() self.assertEqual(match.scores(), [(0, 5), (1, 1), (1, 1)]) + def test_final_score(self): + player1 = axelrod.TitForTat() + player2 = axelrod.Defector() + + match = axelrod.Match((player1, player2), 3) + self.assertEqual(match.final_score(), None) + match.play() + self.assertEqual(match.final_score(), (2, 7)) + + match = axelrod.Match((player2, player1), 3) + self.assertEqual(match.final_score(), None) + match.play() + self.assertEqual(match.final_score(), (7, 2)) + + def test_final_score_per_turn(self): + turns = 3.0 + player1 = axelrod.TitForTat() + player2 = axelrod.Defector() + + match = axelrod.Match((player1, player2), turns) + self.assertEqual(match.final_score_per_turn(), None) + match.play() + self.assertEqual(match.final_score_per_turn(), (2/turns, 7/turns)) + + match = axelrod.Match((player2, player1), turns) + self.assertEqual(match.final_score_per_turn(), None) + match.play() + self.assertEqual(match.final_score_per_turn(), (7/turns, 2/turns)) + + def test_winner(self): + player1 = axelrod.TitForTat() + player2 = axelrod.Defector() + + match = axelrod.Match((player1, player2), 3) + self.assertEqual(match.winner(), None) + match.play() + self.assertEqual(match.winner(), player2) + + match = axelrod.Match((player2, player1), 3) + self.assertEqual(match.winner(), None) + match.play() + self.assertEqual(match.winner(), player2) + + player1 = axelrod.Defector() + match = axelrod.Match((player1, player2), 3) + self.assertEqual(match.winner(), None) + match.play() + self.assertEqual(match.winner(), False) + + def test_cooperation(self): + turns = 3.0 + player1 = axelrod.Cooperator() + player2 = axelrod.Alternator() + + match = axelrod.Match((player1, player2), turns) + self.assertEqual(match.cooperation(), None) + match.play() + self.assertEqual(match.cooperation(), (3, 2)) + + player1 = axelrod.Alternator() + player2 = axelrod.Defector() + + match = axelrod.Match((player1, player2), turns) + self.assertEqual(match.cooperation(), None) + match.play() + self.assertEqual(match.cooperation(), (2, 0)) + + def test_normalised_cooperation(self): + turns = 3.0 + player1 = axelrod.Cooperator() + player2 = axelrod.Alternator() + + match = axelrod.Match((player1, player2), turns) + self.assertEqual(match.normalised_cooperation(), None) + match.play() + self.assertEqual(match.normalised_cooperation(), (3/turns, 2/turns)) + + player1 = axelrod.Alternator() + player2 = axelrod.Defector() + + match = axelrod.Match((player1, player2), turns) + self.assertEqual(match.normalised_cooperation(), None) + match.play() + self.assertEqual(match.normalised_cooperation(), (2/turns, 0/turns)) + def test_sparklines(self): players = (axelrod.Cooperator(), axelrod.Alternator()) match = axelrod.Match(players, 4) diff --git a/axelrod/tests/unit/test_payoff.py b/axelrod/tests/unit/test_payoff.py deleted file mode 100644 index a6c7d8c37..000000000 --- a/axelrod/tests/unit/test_payoff.py +++ /dev/null @@ -1,143 +0,0 @@ -import unittest -from axelrod import Game, Actions -import axelrod.payoff as ap - - -C, D = Actions.C, Actions.D - -class TestPayoff(unittest.TestCase): - - @classmethod - def setUpClass(cls): - - cls.players = ('Alternator', 'TitForTat', 'Random') - - cls.interactions = { - (0, 0): [(C, C), (D, D), (C, C), (D, D), (C, C)], - (0, 1): [(C, C), (D, C), (C, D), (D, C), (C, D)], - (0, 2): [(C, C), (D, C), (C, D), (D, C), (C, D)], - (1, 1): [(C, C), (C, C), (C, C), (C, C), (C, C)], - (1, 2): [(C, D), (D, D), (D, C), (C, D), (D, C)], - (2, 2): [(D, C), (D, D), (D, C), (D, D), (D, D)] - - } - - cls.expected_payoff_matrix = [ - [11, 13, 13], - [13, 15, 11], - [13, 11, 13] - ] - - cls.expected_payoff = [ - [[11.0, 11.0], [13, 13], [15, 12]], - [[13, 13], [15.0, 15.0], [14, 7]], - [[10, 12], [14, 12], [8.0, 14.5]], - ] - - cls.expected_scores = [ - [28, 25], - [27, 20], - [24, 24] - ] - - cls.expected_normalised_scores = [ - [2.8, 2.5], - [2.7, 2.0], - [2.4, 2.4] - ] - - cls.expected_ranking = [0, 2, 1] - cls.expected_ranked_names = ['Alternator', 'Random', 'TitForTat'] - - cls.expected_normalised_payoff = [ - [2.2, 2.6, 2.7], - [2.6, 3.0, 2.1], - [2.2, 2.6, 2.25] - ] - cls.expected_payoff_stddevs = [ - [0.0, 0.0, 0.3], - [0.0, 0.0, 0.7], - [0.2, 0.2, 0.65] - ] - - cls.expected_wins = [[1, 0], [0, 0], [0, 1]] - - cls.diff_means = [ - [0.0, 0.0, 0.5], - [0.0, 0.0, -0.5], - [-0.5, 0.5, 0.0] - ] - - cls.expected_score_diffs = [ - [0.0, 0.0, 0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 0.0, 0.0, -1.0], - [-1.0, 0.0, 0.0, 1.0, 0.0, 0.0] - ] - - def test_player_count(self): - nplayers = ap.player_count(self.interactions) - self.assertEqual(nplayers, 3) - nplayers = ap.player_count({'test': 'test'}) - self.assertEqual(nplayers, 1) - - def test_payoff_matrix(self): - payoff_matrix = ap.payoff_matrix(self.interactions, Game()) - self.assertEqual(payoff_matrix, self.expected_payoff_matrix) - - def test_interaction_payoff(self): - payoff = ap.interaction_payoff(self.interactions[(2, 2)], Game()) - self.assertEqual(payoff, (13, 3)) - - def test_scores(self): - scores = ap.scores(self.expected_payoff) - self.assertEqual(scores, self.expected_scores) - - def test_normalised_scores(self): - scores = ap.normalised_scores(self.expected_scores, 5) - self.assertEqual(scores, self.expected_normalised_scores) - - def test_ranking(self): - ranking = ap.ranking(self.expected_scores) - self.assertEqual(ranking, self.expected_ranking) - - def test_ranked_names(self): - ranked_names = ap.ranked_names( - self.players, self.expected_ranking,) - self.assertEqual(ranked_names, self.expected_ranked_names) - - def test_payoff_diffs_means(self): - #payoff_matrix = ap.payoff_matrix(self.interactions, Game(), 3, 5) - diff_means = ap.payoff_diffs_means(self.expected_payoff, 5) - self.assertEqual(diff_means, self.diff_means) - - def test_score_diffs(self): - #payoff_matrix = ap.payoff_matrix(self.interactions, Game(), 3, 5) - score_diffs = ap.score_diffs(self.expected_payoff, 5) - self.assertEqual(score_diffs, self.expected_score_diffs) - - @staticmethod - def round_matrix(matrix, precision): - return [[round(x, precision) for x in row] for row in matrix] - - def test_normalised_payoff(self): - averages, stddevs = ap.normalised_payoff(self.expected_payoff, 5) - self.assertEqual( - self.round_matrix(averages, 2), self.expected_normalised_payoff) - self.assertEqual( - self.round_matrix(stddevs, 2), self.expected_payoff_stddevs) - - def test_winning_player(self): - test_players = (8, 4) - test_payoffs = (34, 44) - winner = ap.winning_player(test_players, test_payoffs) - self.assertEqual(winner, 4) - test_payoffs = (54, 44) - winner = ap.winning_player(test_players, test_payoffs) - self.assertEqual(winner, 8) - test_payoffs = (34, 34) - winner = ap.winning_player(test_players, test_payoffs) - self.assertEqual(winner, None) - - def test_wins(self): - wins = ap.wins(self.expected_payoff) - self.assertEqual(wins, self.expected_wins) diff --git a/axelrod/tests/unit/test_plot.py b/axelrod/tests/unit/test_plot.py index 18c0acfbf..fa66f27f3 100644 --- a/axelrod/tests/unit/test_plot.py +++ b/axelrod/tests/unit/test_plot.py @@ -1,6 +1,8 @@ import unittest import axelrod +from numpy import mean + matplotlib_installed = True try: import matplotlib.pyplot @@ -12,30 +14,47 @@ class TestPlot(unittest.TestCase): @classmethod def setUpClass(cls): - players = ('Player1', 'Player2', 'Player3') - test_outcome = { - 'payoff': [ - [[0, 10, 21], [10, 0, 16], [16, 16, 0]], - [[0, 10, 21], [8, 0, 20], [16, 16, 0]], - ], - 'cooperation': [] - } - cls.test_result_set = axelrod.ResultSet(players, 5, 2, test_outcome) - cls.expected_boxplot_dataset = [[3.2, 3.2], [3.1, 3.1], [2.6, 2.8]] + cls.players = (axelrod.Alternator(), axelrod.TitForTat(), axelrod.Defector()) + cls.turns = 5 + cls.matches = [ + { + (0,1): axelrod.Match((cls.players[0], cls.players[1]), + turns=cls.turns), + (0,2): axelrod.Match((cls.players[0], cls.players[2]), + turns=cls.turns), + (1,2): axelrod.Match((cls.players[1], cls.players[2]), + turns=cls.turns)} for _ in range(3) + ] # This would not actually be a round robin tournament + # (no cloned matches) + + for rep in cls.matches: + for match in rep.values(): + match.play() + + cls.test_result_set = axelrod.ResultSet(cls.players, cls.matches) + cls.expected_boxplot_dataset = [ + [(17.0 / 5 + 9.0 / 5) / 2 for _ in range(3)], + [(13.0 / 5 + 4.0 / 5) / 2 for _ in range(3)], + [3.0 / 2 for _ in range(3)] + ] cls.expected_boxplot_xticks_locations = [1, 2, 3, 4] - cls.expected_boxplot_xticks_labels = ['Player3', 'Player1', 'Player2'] - cls.expected_boxplot_title = ('Mean score per stage game over 5 turns' - ' repeated 2 times (3 strategies)') + cls.expected_boxplot_xticks_labels = ['Defector', 'Tit For Tat', 'Alternator'] + + cls.expected_lengthplot_dataset = [ + [cls.turns for _ in range(3)], + [cls.turns for _ in range(3)], + [cls.turns for _ in range(3)], + ] + cls.expected_payoff_dataset = [ - [0.0, 3.2, 3.2], - [4.2, 0.0, 2.0], - [3.6, 1.8, 0.0]] - cls.expected_winplot_dataset = ([[1, 2], [0, 1], [0, 0]], - ['Player1', 'Player2', 'Player3']) - cls.expected_winplot_title = "Distributions of wins: 5 turns repeated 2 times (3 strategies)" + [0, mean([9/5.0 for _ in range(3)]), mean([17/5.0 for _ in range(3)])], + [mean([4/5.0 for _ in range(3)]), 0, mean([13/5.0 for _ in range(3)])], + [mean([2/5.0 for _ in range(3)]), mean([13/5.0 for _ in range(3)]), 0] + ] + cls.expected_winplot_dataset = ([[2, 2, 2], [0, 0, 0], [0, 0, 0]], + ['Defector', 'Tit For Tat', 'Alternator']) def test_init(self): - result_set = self.test_result_set plot = axelrod.Plot(self.test_result_set) self.assertEqual(plot.result_set, self.test_result_set) self.assertEqual(matplotlib_installed, plot.matplotlib_installed) @@ -58,10 +77,6 @@ def test_boxplot_xticks_labels(self): plot._boxplot_xticks_labels, self.expected_boxplot_xticks_labels) - def test_boxplot_title(self): - plot = axelrod.Plot(self.test_result_set) - self.assertEqual(plot._boxplot_title, self.expected_boxplot_title) - def test_boxplot(self): if matplotlib_installed: plot = axelrod.Plot(self.test_result_set) @@ -75,10 +90,6 @@ def test_winplot_dataset(self): plot._winplot_dataset, self.expected_winplot_dataset) - def test_winplot_title(self): - plot = axelrod.Plot(self.test_result_set) - self.assertEqual(plot._winplot_title, self.expected_winplot_title) - def test_winplot(self): if matplotlib_installed: plot = axelrod.Plot(self.test_result_set) @@ -86,6 +97,19 @@ def test_winplot(self): else: self.skipTest('matplotlib not installed') + def test_lengthplot_dataset(self): + plot = axelrod.Plot(self.test_result_set) + self.assertSequenceEqual( + plot._winplot_dataset, + self.expected_winplot_dataset) + + def test_lengthplot(self): + if matplotlib_installed: + plot = axelrod.Plot(self.test_result_set) + self.assertIsInstance(plot.lengthplot(), matplotlib.pyplot.Figure) + else: + self.skipTest('matplotlib not installed') + def test_payoff_dataset(self): plot = axelrod.Plot(self.test_result_set) self.assertSequenceEqual( diff --git a/axelrod/tests/unit/test_resultset.py b/axelrod/tests/unit/test_resultset.py index 80c9268ab..c05623440 100644 --- a/axelrod/tests/unit/test_resultset.py +++ b/axelrod/tests/unit/test_resultset.py @@ -1,64 +1,323 @@ import unittest import axelrod +from numpy import mean, std + +from hypothesis import given +from hypothesis.strategies import floats, integers class TestResultSet(unittest.TestCase): @classmethod def setUpClass(cls): - cls.players = ('Alternator', 'TitForTat', 'Random') - cls.test_outcome = { - 'payoff': [ - [[11.0, 13, 15], [13, 15.0, 14], [10, 14, 8.0]], - [[11.0, 13, 12], [13, 15.0, 7], [12, 12, 14.5]], - ], - 'cooperation': [ - [[3, 3, 3], [3, 5, 4], [4, 4, 1]], - [[3, 3, 3], [3, 5, 2], [3, 1, 4]] - ]} + + cls.players = (axelrod.Alternator(), axelrod.TitForTat(), axelrod.Defector()) + cls.turns = 5 + cls.matches = [ + { + (0,1): axelrod.Match((cls.players[0], cls.players[1]), + turns=cls.turns), + (0,2): axelrod.Match((cls.players[0], cls.players[2]), + turns=cls.turns), + (1,2): axelrod.Match((cls.players[1], cls.players[2]), + turns=cls.turns)} for _ in range(3) + ] # This would not actually be a round robin tournament + # (no cloned matches) + + for rep in cls.matches: + for match in rep.values(): + match.play() + + cls.expected_players_to_match_dicts = [{0: [rep[(0, 1)], rep[(0, 2)]], + 1: [rep[(0, 1)], rep[(1, 2)]], + 2: [rep[(1, 2)], rep[(0, 2)]]} + for rep in cls.matches + ] + + + cls.expected_match_lengths =[ + [[0, 5, 5], [5, 0, 5], [5, 5, 0]] + for _ in range(3) + ] + + cls.expected_scores =[ + [15, 15, 15], + [17, 17, 17], + [26, 26, 26] + ] + + cls.expected_wins =[ + [0, 0, 0], + [0, 0, 0], + [2, 2, 2] + ] + + cls.expected_normalised_scores =[ + [3.0 / 2 for _ in range(3)], + [(13.0 / 5 + 4.0 / 5) / 2 for _ in range(3)], + [(17.0 / 5 + 9.0 / 5) / 2 for _ in range(3)], + ] + + cls.expected_ranking = [2, 1, 0] + + cls.expected_ranked_names = ['Defector', 'Tit For Tat', 'Alternator'] cls.expected_null_results_matrix = [ - [[0, 0], [0, 0], [0, 0]], - [[0, 0], [0, 0], [0, 0]], - [[0, 0], [0, 0], [0, 0]], + [[0, 0, 0], [0, 0, 0], [0, 0, 0]], + [[0, 0, 0], [0, 0, 0], [0, 0, 0]], + [[0, 0, 0], [0, 0, 0], [0, 0, 0]], + ] + + cls.expected_payoffs = [ + [[], [13/5.0 for _ in range(3)], [2/5.0 for _ in range(3)]], + [[13/5.0 for _ in range(3)], [], [4/5.0 for _ in range(3)]], + [[17/5.0 for _ in range(3)], [9/5.0 for _ in range(3)], []] + ] + + norm_scores = cls.expected_normalised_scores + cls.expected_score_diffs = [ + [[0.0, 0.0, 0.0], + [0.0, 0.0, 0.0], + [-3.0, -3.0, -3.0]], + [[0.0, 0.0, 0.0], + [0.0, 0.0, 0.0], + [-1.0, -1.0, -1.0]], + [[3.0, 3.0, 3.0], + [1.0, 1.0, 1.0], + [0.0, 0.0, 0.0]], ] - cls.expected_results = { - 'payoff': [ - [[11.0, 11.0], [13, 13], [15, 12]], - [[13, 13], [15.0, 15.0], [14, 7]], - [[10, 12], [14, 12], [8.0, 14.5]], - ], - 'cooperation': [ - [[3, 3], [3, 3], [3, 3]], - [[3, 3], [5, 5], [4, 2]], - [[4, 3], [4, 1], [1, 4]] + cls.expected_payoff_diffs_means = [ + [0.0, 0.0, -3.0], + [0.0, 0.0, -1.0], + [3.0, 1.0, 0.0] + ] + + # Recalculating to deal with numeric imprecision + cls.expected_payoff_matrix = [ + [0, mean([13/5.0 for _ in range(3)]), mean([2/5.0 for _ in range(3)])], + [mean([13/5.0 for _ in range(3)]), 0, mean([4/5.0 for _ in range(3)])], + [mean([17/5.0 for _ in range(3)]), mean([9/5.0 for _ in range(3)]), 0] + ] + + cls.expected_payoff_stddevs = [ + [0, std([13/5.0 for _ in range(3)]), std([2/5.0 for _ in range(3)])], + [std([13/5.0 for _ in range(3)]), 0, std([4/5.0 for _ in range(3)])], + [std([17/5.0 for _ in range(3)]), std([9/5.0 for _ in range(3)]), 0] + ] + + cls.expected_cooperation = [ + [0, 9, 9], + [9, 0, 3], + [0, 0, 0], + ] + + cls.expected_normalised_cooperation = [ + [0, mean([3 / 5.0 for _ in range(3)]), mean([3 / 5.0 for _ in range(3)])], + [mean([3 / 5.0 for _ in range(3)]), 0, mean([1 / 5.0 for _ in range(3)])], + [0, 0, 0], + ] + + cls.expected_vengeful_cooperation = [[2 * element - 1 for element in row] + for row in cls.expected_normalised_cooperation] + + cls.expected_cooperating_rating = [ + 18.0 / 30, + 12.0 / 30, + 0 + ] + + cls.expected_good_partner_matrix = [ + [0, 3, 3], + [3, 0, 3], + [0, 0, 0] + ] + + cls.expected_good_partner_rating = [ + 1.0, + 1.0, + 0 + ] + + + cls.expected_eigenjesus_rating = [ + 0.5547001962252291, + 0.8320502943378436, + 0.0 + ] + + cls.expected_eigenmoses_rating = [ + -0.4578520302117101, + 0.7311328098872432, + 0.5057828909101213 ] - } cls.expected_csv = ( - 'Alternator,Random,TitForTat\n2.8,2.4,2.7\n2.5,2.4,2.0\n') + 'Defector,Tit For Tat,Alternator\n2.6,1.7,1.5\n2.6,1.7,1.5\n2.6,1.7,1.5\n') def test_init(self): - rs = axelrod.ResultSet(self.players, 5, 2, self.test_outcome) + rs = axelrod.ResultSet(self.players, self.matches) self.assertEqual(rs.players, self.players) - self.assertEqual(rs.nplayers, 3) - self.assertEqual(rs.turns, 5) - self.assertEqual(rs.repetitions, 2) - self.assertEqual(rs.outcome, self.test_outcome) - rs = axelrod.ResultSet(self.players, 5, 2, self.test_outcome, False) - self.assertEqual(rs.cooperation, None) + self.assertEqual(rs.nplayers, len(self.players)) + self.assertEqual(rs.matches, self.matches) + self.assertEqual(rs.nrepetitions, len(self.matches)) + + # Test structure of matches + # This is really a test of the test + for rep in rs.matches: + self.assertIsInstance(rep, dict) + for index_pair, match in rep.items(): + self.assertIsInstance(index_pair, tuple) + self.assertIsInstance(match, axelrod.Match) + self.assertEqual(len(match), self.turns) def test_null_results_matrix(self): - rs = axelrod.ResultSet(self.players, 5, 2, self.test_outcome) + rs = axelrod.ResultSet(self.players, self.matches) self.assertEqual( rs._null_results_matrix, self.expected_null_results_matrix) - def test_results(self): - rs = axelrod.ResultSet(self.players, 5, 2, self.test_outcome) - self.assertEqual(rs._results(self.test_outcome), self.expected_results) - self.assertEqual(rs.results, self.expected_results) + def test_match_lengths(self): + rs = axelrod.ResultSet(self.players, self.matches) + self.assertIsInstance(rs.match_lengths, list) + self.assertEqual(len(rs.match_lengths), rs.nrepetitions) + self.assertEqual(rs.match_lengths, self.expected_match_lengths) + + #for rep in rs.match_lengths: + #self.assertIsInstance(rep, list) + #self.assertEqual(len(rep), len(self.players)) + + #for i, opp in enumerate(rep): + #self.assertIsInstance(opp, list) + #self.assertEqual(len(opp), len(self.players)) + + #for j, length in enumerate(opp): + #if i == j: # Specific test for example match setup + #self.assertEqual(length, 0) + #else: + #self.assertEqual(length, self.turns) + + def test_scores(self): + rs = axelrod.ResultSet(self.players, self.matches) + self.assertIsInstance(rs.scores, list) + self.assertEqual(len(rs.scores), rs.nplayers) + self.assertEqual(rs.scores, self.expected_scores) + + def test_ranking(self): + rs = axelrod.ResultSet(self.players, self.matches) + self.assertIsInstance(rs.ranking, list) + self.assertEqual(len(rs.ranking), rs.nplayers) + self.assertEqual(rs.ranking, self.expected_ranking) + + def test_ranked_names(self): + rs = axelrod.ResultSet(self.players, self.matches) + self.assertIsInstance(rs.ranked_names, list) + self.assertEqual(len(rs.ranked_names), rs.nplayers) + self.assertEqual(rs.ranked_names, self.expected_ranked_names) + + def test_wins(self): + rs = axelrod.ResultSet(self.players, self.matches) + self.assertIsInstance(rs.wins, list) + self.assertEqual(len(rs.wins), rs.nplayers) + self.assertEqual(rs.wins, self.expected_wins) + + def test_normalised_scores(self): + rs = axelrod.ResultSet(self.players, self.matches) + self.assertIsInstance(rs.normalised_scores, list) + self.assertEqual(len(rs.normalised_scores), rs.nplayers) + self.assertEqual(rs.normalised_scores, self.expected_normalised_scores) + + def test_payoffs(self): + rs = axelrod.ResultSet(self.players, self.matches) + self.assertIsInstance(rs.payoffs, list) + self.assertEqual(len(rs.payoffs), rs.nplayers) + self.assertEqual(rs.payoffs, self.expected_payoffs) + + def test_payoff_matrix(self): + rs = axelrod.ResultSet(self.players, self.matches) + self.assertIsInstance(rs.payoff_matrix, list) + self.assertEqual(len(rs.payoff_matrix), rs.nplayers) + self.assertEqual(rs.payoff_matrix, self.expected_payoff_matrix) + + def test_score_diffs(self): + rs = axelrod.ResultSet(self.players, self.matches) + self.assertIsInstance(rs.score_diffs, list) + self.assertEqual(len(rs.score_diffs), rs.nplayers) + for i, row in enumerate(rs.score_diffs): + for j, col in enumerate(row): + for k, score in enumerate(col): + self.assertAlmostEqual(score, + self.expected_score_diffs[i][j][k]) + + def test_payoff_diffs_means(self): + rs = axelrod.ResultSet(self.players, self.matches) + self.assertIsInstance(rs.payoff_diffs_means, list) + self.assertEqual(len(rs.payoff_diffs_means), rs.nplayers) + for i, row in enumerate(rs.payoff_diffs_means): + for j, col in enumerate(row): + self.assertAlmostEqual(col, + self.expected_payoff_diffs_means[i][j]) + + def test_payoff_stddevs(self): + rs = axelrod.ResultSet(self.players, self.matches) + self.assertIsInstance(rs.payoff_stddevs, list) + self.assertEqual(len(rs.payoff_stddevs), rs.nplayers) + self.assertEqual(rs.payoff_stddevs, self.expected_payoff_stddevs) + + def test_cooperation(self): + rs = axelrod.ResultSet(self.players, self.matches) + self.assertIsInstance(rs.cooperation, list) + self.assertEqual(len(rs.cooperation), rs.nplayers) + self.assertEqual(rs.cooperation, self.expected_cooperation) + + def test_normalised_cooperation(self): + rs = axelrod.ResultSet(self.players, self.matches) + self.assertIsInstance(rs.normalised_cooperation, list) + self.assertEqual(len(rs.normalised_cooperation), rs.nplayers) + self.assertEqual(rs.normalised_cooperation, + self.expected_normalised_cooperation) + + def test_vengeful_cooperation(self): + rs = axelrod.ResultSet(self.players, self.matches) + self.assertIsInstance(rs.vengeful_cooperation, list) + self.assertEqual(len(rs.vengeful_cooperation), rs.nplayers) + self.assertEqual(rs.vengeful_cooperation, + self.expected_vengeful_cooperation) + + def test_cooperating_rating(self): + rs = axelrod.ResultSet(self.players, self.matches) + self.assertIsInstance(rs.cooperating_rating, list) + self.assertEqual(len(rs.cooperating_rating), rs.nplayers) + self.assertEqual(rs.cooperating_rating, + self.expected_cooperating_rating) + + def test_good_partner_matrix(self): + rs = axelrod.ResultSet(self.players, self.matches) + self.assertIsInstance(rs.good_partner_matrix, list) + self.assertEqual(len(rs.good_partner_matrix), rs.nplayers) + self.assertEqual(rs.good_partner_matrix, + self.expected_good_partner_matrix) + + def test_good_partner_rating(self): + rs = axelrod.ResultSet(self.players, self.matches) + self.assertIsInstance(rs.good_partner_rating, list) + self.assertEqual(len(rs.good_partner_rating), rs.nplayers) + self.assertEqual(rs.good_partner_rating, + self.expected_good_partner_rating) + + def test_eigenjesus_rating(self): + rs = axelrod.ResultSet(self.players, self.matches) + self.assertIsInstance(rs.eigenjesus_rating, list) + self.assertEqual(len(rs.eigenjesus_rating), rs.nplayers) + for j, rate in enumerate(rs.eigenjesus_rating): + self.assertAlmostEqual(rate, self.expected_eigenjesus_rating[j]) + + def test_eigenmoses_rating(self): + rs = axelrod.ResultSet(self.players, self.matches) + self.assertIsInstance(rs.eigenmoses_rating, list) + self.assertEqual(len(rs.eigenmoses_rating), rs.nplayers) + for j, rate in enumerate(rs.eigenmoses_rating): + self.assertAlmostEqual(rate, self.expected_eigenmoses_rating[j]) def test_csv(self): - rs = axelrod.ResultSet(self.players, 5, 2, self.test_outcome) + rs = axelrod.ResultSet(self.players, self.matches) self.assertEqual(rs.csv(), self.expected_csv) diff --git a/axelrod/tests/unit/test_tournament.py b/axelrod/tests/unit/test_tournament.py index 6de227e1b..18f11d68d 100644 --- a/axelrod/tests/unit/test_tournament.py +++ b/axelrod/tests/unit/test_tournament.py @@ -7,7 +7,7 @@ import random from hypothesis import given, example, settings -from hypothesis.strategies import integers, lists, sampled_from, random_module +from hypothesis.strategies import integers, lists, sampled_from, random_module, floats try: # Python 3 @@ -24,6 +24,7 @@ test_repetitions = 5 test_turns = 100 +test_prob_end = .5 class TestTournament(unittest.TestCase): @@ -78,7 +79,6 @@ def test_init(self): self.assertEqual(tournament._parallel_repetitions, 10) anonymous_tournament = axelrod.Tournament(players=self.players) self.assertEqual(anonymous_tournament.name, 'axelrod') - self.assertFalse(tournament._keep_matches) def test_serial_play(self): # Test that we get an instance of ResultSet @@ -91,7 +91,7 @@ def test_serial_play(self): results = tournament.play() self.assertIsInstance(results, axelrod.ResultSet) - # Test that _run_serial_repetitions is called with empty payoffs list + # Test that _run_serial_repetitions is called with empty matches list tournament = axelrod.Tournament( name=self.test_name, players=self.players, @@ -103,8 +103,7 @@ def test_serial_play(self): tournament._run_parallel_repetitions = MagicMock( name='_run_parallel_repetitions') tournament.play() - tournament._run_serial_repetitions.assert_called_once_with( - {'cooperation': [], 'payoff': []}) + tournament._run_serial_repetitions.assert_called_once_with([]) self.assertFalse(tournament._run_parallel_repetitions.called) @given(s=lists(sampled_from(axelrod.strategies), @@ -113,7 +112,7 @@ def test_serial_play(self): turns=integers(min_value=2, max_value=50), repetitions=integers(min_value=2, max_value=4), rm=random_module()) - @settings(max_examples=50, timeout=0) + @settings(max_examples=50, timeout=10) @example(s=test_strategies, turns=test_turns, repetitions=test_repetitions, rm=random.seed(0)) @@ -138,7 +137,6 @@ def test_property_serial_play(self, s, turns, repetitions, rm): repetitions=repetitions) results = tournament.play() self.assertIsInstance(results, axelrod.ResultSet) - self.assertEqual(len(results.cooperation), len(players)) self.assertEqual(results.nplayers, len(players)) self.assertEqual(results.players, players) @@ -154,25 +152,6 @@ def test_parallel_play(self): results = tournament.play() self.assertIsInstance(results, axelrod.ResultSet) - # Test that _run_parallel_repetitions is called with - # one entry in payoffs list - tournament = axelrod.Tournament( - name=self.test_name, - players=self.players, - game=self.game, - turns=200, - repetitions=self.test_repetitions, - processes=2) - tournament._run_serial_repetitions = MagicMock( - name='_run_serial_repetitions') - tournament._run_parallel_repetitions = MagicMock( - name='_run_parallel_repetitions') - tournament.play() - tournament._run_parallel_repetitions.assert_called_once_with({ - 'payoff': [self.expected_payoff], - 'cooperation': [self.expected_cooperation]}) - self.assertFalse(tournament._run_serial_repetitions.called) - def test_build_cache_required(self): # Noisy, no prebuilt cache, empty deterministic cache tournament = axelrod.Tournament( @@ -247,54 +226,30 @@ def test_build_cache(self): tournament._parallel_repetitions, self.test_repetitions - 1) def test_run_single_repetition(self): - outcome = {'payoff': [], 'cooperation': []} + matches = [] tournament = axelrod.Tournament( name=self.test_name, players=self.players, game=self.game, turns=200, repetitions=self.test_repetitions) - tournament._run_single_repetition(outcome) - self.assertEqual(len(outcome['payoff']), 1) - self.assertEqual(len(outcome['cooperation']), 1) - self.assertEqual(outcome['payoff'][0], self.expected_payoff) - self.assertEqual(outcome['cooperation'][0], self.expected_cooperation) - self.assertEqual(len(tournament.matches), 0) - - def test_run_single_repetition_with_keep_matches(self): - outcome = {'payoff': [], 'cooperation': []} - tournament = axelrod.Tournament( - name=self.test_name, - players=self.players, - game=self.game, - turns=200, - repetitions=self.test_repetitions, - keep_matches=True) - tournament._run_single_repetition(outcome) - self.assertEqual(len(outcome['payoff']), 1) - self.assertEqual(len(outcome['cooperation']), 1) - self.assertEqual(outcome['payoff'][0], self.expected_payoff) - self.assertEqual(outcome['cooperation'][0], self.expected_cooperation) + tournament._run_single_repetition(matches) + self.assertEqual(len(tournament.matches), 1) self.assertEqual(len(tournament.matches[0]), 15) def test_run_serial_repetitions(self): - outcome = {'payoff': [], 'cooperation': []} + matches = [] tournament = axelrod.Tournament( name=self.test_name, players=self.players, game=self.game, turns=200, repetitions=self.test_repetitions) - tournament._run_serial_repetitions(outcome) - self.assertEqual(len(outcome['payoff']), self.test_repetitions) - self.assertEqual(len(outcome['cooperation']), self.test_repetitions) - for r in range(self.test_repetitions): - self.assertEqual(outcome['payoff'][r], self.expected_payoff) - self.assertEqual( - outcome['cooperation'][r], self.expected_cooperation) + tournament._run_serial_repetitions(matches) + self.assertEqual(len(tournament.matches), self.test_repetitions) def test_run_parallel_repetitions(self): - outcome = {'payoff': [], 'cooperation': []} + matches = [] tournament = axelrod.Tournament( name=self.test_name, players=self.players, @@ -302,13 +257,10 @@ def test_run_parallel_repetitions(self): turns=200, repetitions=self.test_repetitions, processes=2) - tournament._run_parallel_repetitions(outcome) - self.assertEqual(len(outcome['payoff']), self.test_repetitions) - self.assertEqual(len(outcome['cooperation']), self.test_repetitions) - for r in range(self.test_repetitions): - self.assertEqual(outcome['payoff'][r], self.expected_payoff) - self.assertEqual( - outcome['cooperation'][r], self.expected_cooperation) + tournament._run_parallel_repetitions(matches) + self.assertEqual(len(matches), self.test_repetitions) + for r in matches: + self.assertEqual(len(r.values()), 15) def test_n_workers(self): max_processes = multiprocessing.cpu_count() @@ -371,7 +323,7 @@ def test_start_workers(self): def test_process_done_queue(self): workers = 2 done_queue = multiprocessing.Queue() - outcome = {'payoff': [], 'cooperation': []} + matches = [] tournament = axelrod.Tournament( name=self.test_name, players=self.players, @@ -379,16 +331,11 @@ def test_process_done_queue(self): turns=200, repetitions=self.test_repetitions) for r in range(self.test_repetitions): - done_queue.put( - {'payoff': 'test_payoffs', 'cooperation': 'test_cooperation'}) + done_queue.put([]) for w in range(workers): done_queue.put('STOP') - tournament._process_done_queue(workers, done_queue, outcome) - self.assertEqual(len(outcome['payoff']), self.test_repetitions) - self.assertEqual(len(outcome['cooperation']), self.test_repetitions) - for repetition in range(self.test_repetitions): - self.assertEqual(outcome['payoff'][r], 'test_payoffs') - self.assertEqual(outcome['cooperation'][r], 'test_cooperation') + tournament._process_done_queue(workers, done_queue, matches) + self.assertEqual(len(matches), self.test_repetitions) def test_worker(self): tournament = axelrod.Tournament( @@ -406,66 +353,155 @@ def test_worker(self): done_queue = multiprocessing.Queue() tournament._worker(work_queue, done_queue) for r in range(self.test_repetitions): - output = done_queue.get() - self.assertEqual(output['payoff'], self.expected_payoff) - self.assertEqual(output['cooperation'], self.expected_cooperation) + new_matches = done_queue.get() + self.assertEqual(len(new_matches), 15) + for index_pair, match in new_matches.items(): + self.assertIsInstance(index_pair, tuple) + self.assertIsInstance(match, axelrod.Match) queue_stop = done_queue.get() self.assertEqual(queue_stop, 'STOP') - def test_play_matches(self): + def test_build_result_set(self): tournament = axelrod.Tournament( name=self.test_name, players=self.players, game=self.game, turns=200, repetitions=self.test_repetitions) - matches = { - (0, 0): axelrod.Match((axelrod.Cooperator(), axelrod.Cooperator()), turns=200), - (0, 1): axelrod.Match((axelrod.Cooperator(), axelrod.TitForTat()), turns=200), - (0, 2): axelrod.Match((axelrod.Cooperator(), axelrod.Defector()), turns=200), - (0, 3): axelrod.Match((axelrod.Cooperator(), axelrod.Grudger()), turns=200), - (0, 4): axelrod.Match((axelrod.Cooperator(), axelrod.GoByMajority()), turns=200), - (1, 1): axelrod.Match((axelrod.TitForTat(), axelrod.TitForTat()), turns=200), - (1, 2): axelrod.Match((axelrod.TitForTat(), axelrod.Defector()), turns=200), - (1, 3): axelrod.Match((axelrod.TitForTat(), axelrod.Grudger()), turns=200), - (1, 4): axelrod.Match((axelrod.TitForTat(), axelrod.GoByMajority()), turns=200), - (2, 2): axelrod.Match((axelrod.Defector(), axelrod.Defector()), turns=200), - (2, 3): axelrod.Match((axelrod.Defector(), axelrod.Grudger()), turns=200), - (2, 4): axelrod.Match((axelrod.Defector(), axelrod.GoByMajority()), turns=200), - (3, 3): axelrod.Match((axelrod.Grudger(), axelrod.Grudger()), turns=200), - (3, 4): axelrod.Match((axelrod.Grudger(), axelrod.GoByMajority()), turns=200), - (4, 4): axelrod.Match((axelrod.GoByMajority(), axelrod.GoByMajority()), turns=200), - } - results = tournament._play_matches(matches) - self.assertNotIn('matches', results) - self.assertEqual(results['payoff'], self.expected_payoff) + results = tournament._build_result_set() + self.assertIsInstance(results, axelrod.ResultSet) @given(turns=integers(min_value=1, max_value=200)) @example(turns=3) @example(turns=200) - def test_keep_matches(self, turns): + def test_play_matches(self, turns): tournament = axelrod.Tournament( name=self.test_name, players=self.players, game=self.game, - repetitions=self.test_repetitions, - keep_matches=True) + repetitions=self.test_repetitions) matches = { - (0, 0): axelrod.Match((axelrod.Cooperator(), axelrod.Cooperator()), turns=3), - (0, 1): axelrod.Match((axelrod.Cooperator(), axelrod.TitForTat()), turns=3), - (0, 2): axelrod.Match((axelrod.Cooperator(), axelrod.Defector()), turns=3), - (0, 3): axelrod.Match((axelrod.Cooperator(), axelrod.Grudger()), turns=3), - (0, 4): axelrod.Match((axelrod.Cooperator(), axelrod.GoByMajority()), turns=3), - (1, 1): axelrod.Match((axelrod.TitForTat(), axelrod.TitForTat()), turns=3), - (1, 2): axelrod.Match((axelrod.TitForTat(), axelrod.Defector()), turns=3), - (1, 3): axelrod.Match((axelrod.TitForTat(), axelrod.Grudger()), turns=3), - (1, 4): axelrod.Match((axelrod.TitForTat(), axelrod.GoByMajority()), turns=3), - (2, 2): axelrod.Match((axelrod.Defector(), axelrod.Defector()), turns=3), - (2, 3): axelrod.Match((axelrod.Defector(), axelrod.Grudger()), turns=3), - (2, 4): axelrod.Match((axelrod.Defector(), axelrod.GoByMajority()), turns=3), - (3, 3): axelrod.Match((axelrod.Grudger(), axelrod.Grudger()), turns=3), - (3, 4): axelrod.Match((axelrod.Grudger(), axelrod.GoByMajority()), turns=3), - (4, 4): axelrod.Match((axelrod.GoByMajority(), axelrod.GoByMajority()), turns=3), + (0, 0): axelrod.Match((axelrod.Cooperator(), axelrod.Cooperator()), turns=turns), + (0, 1): axelrod.Match((axelrod.Cooperator(), axelrod.TitForTat()), turns=turns), + (0, 2): axelrod.Match((axelrod.Cooperator(), axelrod.Defector()), turns=turns), + (0, 3): axelrod.Match((axelrod.Cooperator(), axelrod.Grudger()), turns=turns), + (0, 4): axelrod.Match((axelrod.Cooperator(), axelrod.GoByMajority()), turns=turns), + (1, 1): axelrod.Match((axelrod.TitForTat(), axelrod.TitForTat()), turns=turns), + (1, 2): axelrod.Match((axelrod.TitForTat(), axelrod.Defector()), turns=turns), + (1, 3): axelrod.Match((axelrod.TitForTat(), axelrod.Grudger()), turns=turns), + (1, 4): axelrod.Match((axelrod.TitForTat(), axelrod.GoByMajority()), turns=turns), + (2, 2): axelrod.Match((axelrod.Defector(), axelrod.Defector()), turns=turns), + (2, 3): axelrod.Match((axelrod.Defector(), axelrod.Grudger()), turns=turns), + (2, 4): axelrod.Match((axelrod.Defector(), axelrod.GoByMajority()), turns=turns), + (3, 3): axelrod.Match((axelrod.Grudger(), axelrod.Grudger()), turns=turns), + (3, 4): axelrod.Match((axelrod.Grudger(), axelrod.GoByMajority()), turns=turns), + (4, 4): axelrod.Match((axelrod.GoByMajority(), axelrod.GoByMajority()), turns=turns), } - output = tournament._play_matches(matches) - self.assertEqual(len(output['matches']), 15) + tournament._play_matches(matches) + for m in matches.values(): + self.assertEqual(len(m), turns) + # Check that the name of the winner is in the list of names of + # players in the tournament (the way the matches are created these + # are not the same players). + self.assertIn(str(m.winner()), [str(p) for p in tournament.players] + ['False']) + + +class TestProbEndTournament(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.game = axelrod.Game() + cls.players = [s() for s in test_strategies] + cls.test_name = 'test' + cls.test_repetitions = test_repetitions + cls.test_prob_end = test_prob_end + + def test_init(self): + tournament = axelrod.ProbEndTournament( + name=self.test_name, + players=self.players, + game=self.game, + prob_end=self.test_prob_end, + noise=0.2) + self.assertEqual(tournament.tournament_type.prob_end, tournament.prob_end) + self.assertEqual(len(tournament.players), len(test_strategies)) + self.assertEqual( + tournament.players[0].tournament_attributes['length'], + float("inf") + ) + self.assertIsInstance( + tournament.players[0].tournament_attributes['game'], axelrod.Game + ) + self.assertEqual(tournament.game.score(('C', 'C')), (3, 3)) + self.assertEqual(tournament.turns, float("inf")) + self.assertEqual(tournament.repetitions, 10) + self.assertEqual(tournament.name, 'test') + self.assertEqual(tournament._processes, None) + self.assertFalse(tournament.prebuilt_cache) + self.assertTrue(tournament._with_morality) + self.assertIsInstance(tournament._logger, logging.Logger) + self.assertEqual(tournament.deterministic_cache, {}) + self.assertEqual(tournament.noise, 0.2) + self.assertEqual(tournament._parallel_repetitions, 10) + anonymous_tournament = axelrod.Tournament(players=self.players) + self.assertEqual(anonymous_tournament.name, 'axelrod') + + @given(s=lists(sampled_from(axelrod.strategies), + min_size=2, # Errors are returned if less than 2 strategies + max_size=5, unique=True), + prob_end=floats(min_value=.1, max_value=.9), + repetitions=integers(min_value=2, max_value=4), + rm=random_module()) + @settings(max_examples=50, timeout=0) + @example(s=test_strategies, prob_end=test_prob_end, + repetitions=test_repetitions, + rm=random.seed(0)) + def test_build_cache_never_required(self, s, prob_end, repetitions, rm): + """ + As the matches have a sampled length a cache is never required. + """ + players = [strat() for strat in s] + + tournament = axelrod.ProbEndTournament( + name=self.test_name, + players=players, + game=self.game, + prob_end=prob_end, + repetitions=repetitions) + self.assertFalse(tournament._build_cache_required()) + + + @given(s=lists(sampled_from(axelrod.strategies), + min_size=2, # Errors are returned if less than 2 strategies + max_size=5, unique=True), + prob_end=floats(min_value=.1, max_value=.9), + repetitions=integers(min_value=2, max_value=4), + rm=random_module()) + @settings(max_examples=50, timeout=10) + @example(s=test_strategies, prob_end=.2, repetitions=test_repetitions, + rm=random.seed(0)) + + # These two examples are to make sure #465 is fixed. + # As explained there: https://github.com/Axelrod-Python/Axelrod/issues/465, + # these two examples were identified by hypothesis. + @example(s=[axelrod.BackStabber, axelrod.MindReader], prob_end=.2, repetitions=1, + rm=random.seed(0)) + @example(s=[axelrod.ThueMorse, axelrod.MindReader], prob_end=.2, repetitions=1, + rm=random.seed(0)) + def test_property_serial_play(self, s, prob_end, repetitions, rm): + """Test serial play using hypothesis""" + # Test that we get an instance of ResultSet + + players = [strat() for strat in s] + + tournament = axelrod.ProbEndTournament( + name=self.test_name, + players=players, + game=self.game, + prob_end=prob_end, + repetitions=repetitions) + results = tournament.play() + self.assertIsInstance(results, axelrod.ResultSet) + self.assertEqual(results.nplayers, len(players)) + self.assertEqual(results.players, players) + self.assertEqual(len(results.matches), repetitions) diff --git a/axelrod/tests/unit/test_tournament_type.py b/axelrod/tests/unit/test_tournament_type.py index 9d0d880d8..5b6a3f582 100644 --- a/axelrod/tests/unit/test_tournament_type.py +++ b/axelrod/tests/unit/test_tournament_type.py @@ -1,6 +1,9 @@ import unittest import axelrod +from hypothesis import given, example +from hypothesis.strategies import floats, random_module + test_strategies = [ axelrod.Cooperator, axelrod.TitForTat, @@ -36,6 +39,13 @@ class TestRoundRobin(unittest.TestCase): def setUpClass(cls): cls.players = [s() for s in test_strategies] + def test_build_single_match(self): + rr = axelrod.RoundRobin(self.players, test_turns, {}) + match = rr.build_single_match((self.players[0], self.players[1])) + self.assertIsInstance(match, axelrod.Match) + self.assertEqual(len(match), rr.turns) + + def test_build_matches(self): rr = axelrod.RoundRobin(self.players, test_turns, {}) matches = rr.build_matches() @@ -59,3 +69,67 @@ def test_build_matches(self): (4, 4) ] self.assertEqual(sorted(match_definitions), expected_match_definitions) + + +class TestProbEndRoundRobin(unittest.TestCase): + + @classmethod + def setUpClass(cls): + cls.players = [s() for s in test_strategies] + + @given(prob_end=floats(min_value=0, max_value=1), rm=random_module()) + def test_build_matches(self, prob_end, rm): + rr = axelrod.ProbEndRoundRobin(self.players, prob_end, {}) + matches = rr.build_matches() + match_definitions = [ + (match) for match in matches] + expected_match_definitions = [ + (0, 0), + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (1, 1), + (1, 2), + (1, 3), + (1, 4), + (2, 2), + (2, 3), + (2, 4), + (3, 3), + (3, 4), + (4, 4) + ] + self.assertEqual(sorted(match_definitions), expected_match_definitions) + + @given(prob_end=floats(min_value=0.1, max_value=0.5), rm=random_module()) + def test_build_matches_different_length(self, prob_end, rm): + """ + If prob end is not 0 or 1 then the matches should all have different + length + + Theoretically, this test could fail as it's probabilistically possible + to sample all games with same length. + """ + rr = axelrod.ProbEndRoundRobin(self.players, prob_end, {}) + matches = rr.build_matches() + match_lengths = [len(m) for m in matches.values()] + self.assertNotEqual(min(match_lengths), max(match_lengths)) + + @given(prob_end=floats(min_value=0, max_value=1), rm=random_module()) + def test_sample_length(self, prob_end, rm): + rr = axelrod.ProbEndRoundRobin(self.players, prob_end, {}) + self.assertGreaterEqual(rr.sample_length(prob_end), 1) + try: + self.assertIsInstance(rr.sample_length(prob_end), int) + except AssertionError: + self.assertEqual(rr.sample_length(prob_end), float("inf")) + + @given(prob_end=floats(min_value=.1, max_value=1), rm=random_module()) + def test_build_single_match(self, prob_end, rm): + rr = axelrod.ProbEndRoundRobin(self.players, prob_end, {}) + match = rr.build_single_match((self.players[0], self.players[1])) + + self.assertIsInstance(match, axelrod.Match) + self.assertGreaterEqual(len(match), 1) + self.assertLessEqual(len(match), float("inf")) diff --git a/axelrod/tournament.py b/axelrod/tournament.py index abfbcbc21..b9ca2d899 100644 --- a/axelrod/tournament.py +++ b/axelrod/tournament.py @@ -5,9 +5,7 @@ from .game import Game from .result_set import ResultSet -from .tournament_type import RoundRobin -from .payoff import payoff_matrix -from .cooperation import cooperation_matrix +from .tournament_type import RoundRobin, ProbEndRoundRobin class Tournament(object): @@ -15,8 +13,7 @@ class Tournament(object): def __init__(self, players, tournament_type=RoundRobin, name='axelrod', game=None, turns=200, repetitions=10, processes=None, - prebuilt_cache=False, noise=0, with_morality=True, - keep_matches=False): + prebuilt_cache=False, noise=0, with_morality=True): """ Parameters ---------- @@ -55,11 +52,9 @@ def __init__(self, players, tournament_type=RoundRobin, name='axelrod', self.tournament_type = tournament_type( players, turns, self.deterministic_cache) self._with_morality = with_morality - self._keep_matches = keep_matches self._parallel_repetitions = repetitions self._processes = processes self._logger = logging.getLogger(__name__) - self._outcome = {'payoff': [], 'cooperation': []} self.matches = [] @property @@ -87,19 +82,28 @@ def play(self): axelrod.ResultSet """ if self._processes is None: - self._run_serial_repetitions(self._outcome) + self._run_serial_repetitions(self.matches) else: if self._build_cache_required(): - self._build_cache(self._outcome) - self._run_parallel_repetitions(self._outcome) + self._build_cache(self.matches) + self._run_parallel_repetitions(self.matches) - self.result_set = ResultSet( + self.result_set = self._build_result_set() + return self.result_set + + def _build_result_set(self): + """ + Build the result set (used by the play method) + + Returns + ------- + axelrod.ResultSet + """ + result_set = ResultSet( players=self.players, - turns=self.turns, - repetitions=self.repetitions, - outcome=self._outcome, + matches=self.matches, with_morality=self._with_morality) - return self.result_set + return result_set def _build_cache_required(self): """ @@ -111,54 +115,51 @@ def _build_cache_required(self): len(self.deterministic_cache) == 0 or not self.prebuilt_cache)) - def _build_cache(self, outcome): + def _build_cache(self, matches): """ For parallel processing, this runs a single round robin in order to build the deterministic cache. Parameters ---------- - outcome : dictionary - The outcome dictionary to update with results + matches : list + The list of matches to update """ self._logger.debug('Playing first round robin to build cache') - self._run_single_repetition(outcome) + self._run_single_repetition(matches) self._parallel_repetitions -= 1 - def _run_single_repetition(self, outcome): + def _run_single_repetition(self, matches): """ - Runs a single round robin and updates the outcome dictionary. + Runs a single round robin and updates the matches list. """ - matches = self.tournament_type.build_matches( + new_matches = self.tournament_type.build_matches( cache_mutable=True, noise=self.noise) - output = self._play_matches(matches) - outcome['payoff'].append(output['payoff']) - outcome['cooperation'].append(output['cooperation']) - if self._keep_matches: - self.matches.append(output['matches']) + self._play_matches(new_matches) + self.matches.append(new_matches) - def _run_serial_repetitions(self, outcome): + def _run_serial_repetitions(self, matches): """ Runs all repetitions of the round robin in serial. Parameters ---------- - outcome : dictionary - The outcome dictionary to update with results + matches : list + The list of matches per repetition to update with results """ self._logger.debug('Playing %d round robins' % self.repetitions) for repetition in range(self.repetitions): - self._run_single_repetition(outcome) + self._run_single_repetition(matches) return True - def _run_parallel_repetitions(self, outcome): + def _run_parallel_repetitions(self, matches): """ Run all except the first round robin using parallel processing. Parameters ---------- - outcome : dictionary - The outcome dictionary to update with results + matches : list + The list of matches per repetition to update with results """ # At first sight, it might seem simpler to use the multiprocessing Pool # Class rather than Processes and Queues. However, Pool can only accept @@ -174,7 +175,7 @@ def _run_parallel_repetitions(self, outcome): 'Playing %d round robins with %d parallel processes' % (self._parallel_repetitions, workers)) self._start_workers(workers, work_queue, done_queue) - self._process_done_queue(workers, done_queue, outcome) + self._process_done_queue(workers, done_queue, matches) return True @@ -212,9 +213,9 @@ def _start_workers(self, workers, work_queue, done_queue): process.start() return True - def _process_done_queue(self, workers, done_queue, outcome): + def _process_done_queue(self, workers, done_queue, matches): """ - Retrieves the outcome dictionaries from the parallel sub-processes + Retrieves the matches from the parallel sub-processes Parameters ---------- @@ -222,19 +223,16 @@ def _process_done_queue(self, workers, done_queue, outcome): The number of sub-processes in existence done_queue : multiprocessing.Queue A queue containing the output dictionaries from each round robin - outcome : dictionary - The outcome dictionary to update with results + matches : list + The list of matches per repetition to update with results """ stops = 0 while stops < workers: - output = done_queue.get() - if output == 'STOP': + new_matches = done_queue.get() + if new_matches == 'STOP': stops += 1 else: - outcome['payoff'].append(output['payoff']) - outcome['cooperation'].append(output['cooperation']) - if self._keep_matches: - self.matches.append(outcome['matches']) + matches.append(new_matches) return True def _worker(self, work_queue, done_queue): @@ -249,10 +247,10 @@ def _worker(self, work_queue, done_queue): A queue containing the output dictionaries from each round robin """ for repetition in iter(work_queue.get, 'STOP'): - matches = self.tournament_type.build_matches( + new_matches = self.tournament_type.build_matches( cache_mutable=False, noise=self.noise) - output = self._play_matches(matches) - done_queue.put(output) + self._play_matches(new_matches) + done_queue.put(new_matches) done_queue.put('STOP') return True @@ -265,24 +263,59 @@ def _play_matches(self, matches): matches : dictionary Mapping a tuple of player index numbers to an axelrod Match object - Returns - ------- - dictionary - Containing the payoff and cooperation matrices """ - interactions = {} - if self._keep_matches: - matches_to_keep = [] - - for key, match in matches.items(): - interactions[key] = match.play() - if self._keep_matches: - matches_to_keep.append(match) - - payoff = payoff_matrix(interactions, self.game) - cooperation = cooperation_matrix(interactions) - - output = {'payoff': payoff, 'cooperation': cooperation} - if self._keep_matches: - output['matches'] = matches_to_keep - return output + for match in matches.values(): + match.play() + + +class ProbEndTournament(Tournament): + """ + A tournament in which the player don't know the length of a given match + (currently implemented by setting this to be infinite). The length of a + match is equivalent to randomly sampling after each round whether or not to + continue. + """ + + def __init__(self, players, tournament_type=ProbEndRoundRobin, + name='axelrod', game=None, prob_end=.5, repetitions=10, + processes=None, prebuilt_cache=False, noise=0, + with_morality=True): + """ + Parameters + ---------- + players : list + A list of axelrod.Player objects + tournament_type : class + A class that must be descended from axelrod.TournamentType + name : string + A name for the tournament + game : axelrod.Game + The game object used to score the tournament + prob_end : a float + The probability of a given match ending + repetitions : integer + The number of times the round robin should be repeated + processes : integer + The number of processes to be used for parallel processing + prebuilt_cache : boolean + Whether a cache has been passed in from an external object + noise : float + The probability that a player's intended action should be flipped + with_morality : boolean + Whether morality metrics should be calculated + """ + super(ProbEndTournament, self).__init__( + players, name=name, game=game, turns=float("inf"), + repetitions=repetitions, processes=processes, + prebuilt_cache=prebuilt_cache, noise=noise, + with_morality=with_morality) + + self.prob_end = prob_end + self.tournament_type = ProbEndRoundRobin( + players, prob_end, self.deterministic_cache) + + def _build_cache_required(self): + """ + A cache is never required (as every Match length can be different) + """ + return False diff --git a/axelrod/tournament_type.py b/axelrod/tournament_type.py index 16fe50805..7e2d5ae64 100644 --- a/axelrod/tournament_type.py +++ b/axelrod/tournament_type.py @@ -1,3 +1,6 @@ +import random +from math import ceil, log + from .match import Match @@ -41,6 +44,9 @@ def opponents(self, players): def build_matches(): raise NotImplementedError() + def build_single_match(): + raise NotImplementedError() + class RoundRobin(TournamentType): @@ -67,8 +73,82 @@ def build_matches(self, cache_mutable=True, noise=0): for player2_index in range(player1_index, len(self.players)): pair = ( self.players[player1_index], self.opponents[player2_index]) - match = Match( - pair, self.turns, self.deterministic_cache, - cache_mutable, noise) + match = self.build_single_match(pair, cache_mutable, noise) matches[(player1_index, player2_index)] = match return matches + + def build_single_match(self, pair, cache_mutable=True, noise=0): + """Create a single match for a given pair""" + return Match(pair, self.turns, self.deterministic_cache, + cache_mutable, noise) + + +class ProbEndRoundRobin(RoundRobin): + + clone_opponents = True + + def __init__(self, players, prob_end, deterministic_cache): + """ + A class to represent a tournament type for which the players do not + know the length of the Match (to their knowledge it is infinite) but + that ends with given probability. + + Parameters + ---------- + players : list + A list of axelrod.Player objects + prob_end : float + The probability that a turn of a Match is the last + deterministic_cache : dictionary + A cache of resulting actions for deterministic matches + """ + super(ProbEndRoundRobin, self).__init__( + players, turns=float("inf"), + deterministic_cache=deterministic_cache) + self.prob_end = prob_end + + def build_matches(self, cache_mutable=False, noise=0): + """Build the matches but with cache_mutable False""" + return super(ProbEndRoundRobin, self).build_matches(False, noise) + + def build_single_match(self, pair, cache_mutable=False, noise=0): + """Create a single match for a given pair""" + return Match(pair, self.sample_length(self.prob_end), + self.deterministic_cache, cache_mutable, noise) + + def sample_length(self, prob_end): + """ + Sample length of a game. + + This is using inverse random sample on a probability density function + given by: + + f(n) = p_end * (1 - p_end) ^ (n - 1) + + (So the probability of length n is given by f(n)) + + Which gives cumulative distribution function + : + + F(n) = 1 - (1 - p_end) ^ n + + (So the probability of length less than or equal to n is given by F(n)) + + Which gives for given x = F(n) (ie the random sample) gives n: + + n = ceil((ln(1-x)/ln(1-p_end))) + + This approach of sampling from a distribution is called inverse + transform sampling + . + + Note that this corresponds to sampling at the end of every turn whether + or not the Match ends. + """ + try: + x = random.random() + return int(ceil(log(1 - x) / log(1 - self.prob_end))) + except ZeroDivisionError: + return float("inf") + except ValueError: + return 1 diff --git a/docs/tutorials/further_topics/_static/prob_end_tournaments/prob_end_boxplot.svg b/docs/tutorials/further_topics/_static/prob_end_tournaments/prob_end_boxplot.svg new file mode 100644 index 000000000..414ddccfb --- /dev/null +++ b/docs/tutorials/further_topics/_static/prob_end_tournaments/prob_end_boxplot.svg @@ -0,0 +1,2049 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/tutorials/further_topics/_static/prob_end_tournaments/prob_end_lengthplot.svg b/docs/tutorials/further_topics/_static/prob_end_tournaments/prob_end_lengthplot.svg new file mode 100644 index 000000000..1e55ce2c8 --- /dev/null +++ b/docs/tutorials/further_topics/_static/prob_end_tournaments/prob_end_lengthplot.svg @@ -0,0 +1,1792 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/tutorials/further_topics/_static/prob_end_tournaments/prob_end_stackplot.svg b/docs/tutorials/further_topics/_static/prob_end_tournaments/prob_end_stackplot.svg new file mode 100644 index 000000000..a4537f930 --- /dev/null +++ b/docs/tutorials/further_topics/_static/prob_end_tournaments/prob_end_stackplot.svg @@ -0,0 +1,2053 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/tutorials/further_topics/_static/prob_end_tournaments/prob_end_stackplot_low_p.svg b/docs/tutorials/further_topics/_static/prob_end_tournaments/prob_end_stackplot_low_p.svg new file mode 100644 index 000000000..df46e6c11 --- /dev/null +++ b/docs/tutorials/further_topics/_static/prob_end_tournaments/prob_end_stackplot_low_p.svg @@ -0,0 +1,2053 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/tutorials/further_topics/index.rst b/docs/tutorials/further_topics/index.rst index 984a1b4d0..d81bd1e43 100644 --- a/docs/tutorials/further_topics/index.rst +++ b/docs/tutorials/further_topics/index.rst @@ -10,5 +10,6 @@ Contents: :maxdepth: 2 classification_of_strategies.rst - morality_metrics.rst creating_matches.rst + morality_metrics.rst + probabilistict_end_tournaments.rst diff --git a/docs/tutorials/further_topics/morality_metrics.rst b/docs/tutorials/further_topics/morality_metrics.rst index 5e0dfb965..fa61529eb 100644 --- a/docs/tutorials/further_topics/morality_metrics.rst +++ b/docs/tutorials/further_topics/morality_metrics.rst @@ -23,7 +23,7 @@ A matrix of cooperation rates is available within a tournament's ResultSet:: ... axl.TitForTat(), axl.Grudger()] >>> tournament = axl.Tournament(strategies) >>> results = tournament.play() - >>> results.normalised_cooperation + >>> [[round(float(ele), 3) for ele in row] for row in results.normalised_cooperation] [[1.0, 1.0, 1.0, 1.0], [0.0, 0.0, 0.0, 0.0], [1.0, 0.005, 1.0, 1.0], [1.0, 0.005, 1.0, 1.0]] There is also a 'good partner' matrix showing how often a player cooperated at @@ -35,7 +35,7 @@ least as much as its opponent:: Each of the metrics described in Tyler's paper is available as follows (here they are rounded to 2 digits):: >>> [round(ele, 2) for ele in results.cooperating_rating] - [1.0, 0.0, 0.75, 0.75] + [1.0, 0.0, 0.67, 0.67] >>> [round(ele, 2) for ele in results.good_partner_rating] [1.0, 0.0, 1.0, 1.0] >>> [round(ele, 2) for ele in results.eigenjesus_rating] diff --git a/docs/tutorials/further_topics/probabilistict_end_tournaments.rst b/docs/tutorials/further_topics/probabilistict_end_tournaments.rst new file mode 100644 index 000000000..a83a69418 --- /dev/null +++ b/docs/tutorials/further_topics/probabilistict_end_tournaments.rst @@ -0,0 +1,84 @@ +Probabilistic Ending Tournaments +================================ + +It is possible to create a tournament where the length of each Match is not +constant for all encounters: after each turn the Match ends with a given +probability:: + + >>> import axelrod as axl + >>> strategies = [axl.Cooperator(), axl.Defector(), + ... axl.TitForTat(), axl.Grudger()] + >>> tournament = axl.ProbEndTournament(strategies, prob_end=0.5) + + +We can view the results in a similar way as for described in +:ref:`payoff-matrix`:: + + >>> results = tournament.play() + >>> m = results.payoff_matrix + >>> for row in m: # doctest: +SKIP + ... print([round(ele, 1) for ele in row]) # Rounding output # doctest: +SKIP + + [3.0, 0.0, 3.0, 3.0] + [5.0, 1.0, 3.7, 3.6] + [3.0, 0.3, 3.0, 3.0] + [3.0, 0.4, 3.0, 3.0] + + +We see that :code:`Cooperator` always scores 0 against :code:`Defector` but +other scores seem variable as they are effected by the length of each match. + +We can (as before) obtain the ranks for our players:: + + >>> results.ranked_names # doctest: +SKIP + ['Defector', 'Tit For Tat', 'Grudger', 'Cooperator'] + +We can plot the results:: + + >>> plot = axl.Plot(results) + >>> p = plot.boxplot() + >>> p.show() + +.. image:: _static/prob_end_tournaments/prob_end_boxplot.svg + :width: 50% + :align: center + +We can also view the length of the matches player by each player (here plotted +with a high number of repetitions we see that the expected length of Match is +obtained :math:`\approx 4`):: + + >>> p = plot.lengthplot() + >>> p.show() + +.. image:: _static/prob_end_tournaments/prob_end_lengthplot.svg + :width: 50% + :align: center + +Just as for a constant length tournament and as described in +:ref:`ecological-variant` we can look at the strategies in an evolutionary +context:: + + >>> eco = axl.Ecosystem(results) + >>> eco.reproduce(100) + >>> p = plot.stackplot(eco) + >>> p.show() + +.. image:: _static/prob_end_tournaments/prob_end_stackplot.svg + :width: 50% + :align: center + +Note that if we repeat the above but using a much lower probability of the +match ending (thus increasing the importance of reputation):: + + >>> tournament = axl.ProbEndTournament(strategies, prob_end=0.01) + >>> results = tournament.play() + >>> eco = axl.Ecosystem(results) + >>> eco.reproduce(100) + >>> p = plot.stackplot(eco) + >>> p.show() + +We see that the :code:`Cooperation` now takes over: + +.. image:: _static/prob_end_tournaments/prob_end_stackplot_low_p.svg + :width: 50% + :align: center diff --git a/docs/tutorials/getting_started/ecological_variant.rst b/docs/tutorials/getting_started/ecological_variant.rst index df8f2900f..e08222a20 100644 --- a/docs/tutorials/getting_started/ecological_variant.rst +++ b/docs/tutorials/getting_started/ecological_variant.rst @@ -1,3 +1,5 @@ +.. _ecological-variant: + Ecological Variant ================== diff --git a/docs/tutorials/getting_started/interactions.rst b/docs/tutorials/getting_started/interactions.rst index 8f34b6533..464f4b6a0 100644 --- a/docs/tutorials/getting_started/interactions.rst +++ b/docs/tutorials/getting_started/interactions.rst @@ -5,22 +5,19 @@ This tutorial will show you briefly how to access the detailed interaction results corresponding to the tournament. To access the detailed interaction results we create a tournament as usual -(see :ref:`getting-started`), setting the keep_matches=True parameter to save -the interactions.:: +(see :ref:`getting-started`):: >>> import axelrod as axl >>> strategies = [ ... axl.Cooperator(), axl.Defector(), ... axl.TitForTat(), axl.Grudger()] - >>> tournament = axl.Tournament( - ... strategies, turns=3, repetitions=1, keep_matches=True) + >>> tournament = axl.Tournament(strategies, turns=3, repetitions=1) >>> results = tournament.play() -The detailed interactions are now available to us. The tournament object has -a 'matches' attribute which is a list of axelrod.Match objects. (Actually, it's -a list of lists: one list for each repetition which, in turn, has a list of -Match objects). Each match object holds the pair of axelrod.Player objects for -that match and the history of their interactions:: +The tournament object has a 'matches' attribute which is a list of axelrod.Match +objects. (Actually, it's a list of lists: one list for each repetition which, in +turn, has a list of Match objects). Each match object holds the pair of +axelrod.Player objects for that match and the history of their interactions:: >>> for match in tournament.matches[0]: ... player1 = match.player1.name diff --git a/docs/tutorials/getting_started/payoff_matrix.rst b/docs/tutorials/getting_started/payoff_matrix.rst index 98a4c44a8..f9ccc06c7 100644 --- a/docs/tutorials/getting_started/payoff_matrix.rst +++ b/docs/tutorials/getting_started/payoff_matrix.rst @@ -1,3 +1,5 @@ +.. _payoff-matrix: + Accessing the payoff matrix =========================== diff --git a/test b/test index e96b8edb5..957709281 100755 --- a/test +++ b/test @@ -1,3 +1,14 @@ #!/usr/bin/env bash python -m unittest discover axelrod/tests/ +rm basic_strategies_boxplot.svg +rm basic_strategies_payoff.svg +rm basic_strategies_pdplot.svg +rm basic_strategies_reproduce.svg +rm basic_strategies_sdvplot.svg +rm basic_strategies_winplot.svg +rm test_boxplot.svg +rm test_payoff.svg +rm test_pdplot.svg +rm test_sdvplot.svg +rm test_winplot.svg python doctests.py