From 2e1f4df98c4484bf839d79707d619499806f12ce Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Wed, 24 Jan 2018 12:39:24 +0000 Subject: [PATCH 01/11] Re write the result set At present the result set plays the matches in parallel followed by a (sometimes computationally expensive) single process of reading in and analysing the interactions. TLDR: This changes how the internal result set calculations are being done. They are more efficiently calculated. This happens by doing the following: 1. The various match by match calculations are done by the tournament (the winner of a match, the cooperation count, the score etc...). 2. This calculations are written to file (at present we just write the actual actions of an interaction). 3. The form of this file has also changed: for every match there are 2 rows. One row corresponds to each player. This might seem costly storage wise but is done to enable faster analysis (more on that later). 4. The analysis is now done entirely using `dask`: "Dask is a flexible parallel computing library for analytic computing." (https://dask.pydata.org/). This ensures all calculations are done on disk (so no huge memory problems) but also that they can be done **in parallel**. This is all done using the nice Pandas-like API that dask has so essentially all calculations for the result set are just done by a few `groupby` statements. 5. There is *some* work being done outside of dask but that's just reshaping data. `dask` outputs `pandas.Series` and to be consistent with our current setup these are changes to be lists of list etc... **Some user facing changes** (which is why I suggest this would be for a `v4.0.0` release): - The `result_set.interactions` is no longer possible. This is a choice and not forced by the redesign: I don't think this is ever necessary or helpful. The data file can always be read in. - The ability to `tournament.play(in_memory=True)` has been removed. Again, not entirely a forced change (although it would be a tiny bit of work to allow this). Given the recent work to make everything work on Windows I don't think this is necessary and has allowed for a big deletion of code (which is a good thing re maintenance). - The interactions data file is now in a different format, this is forced by the design choice. - I have made a slight modification to `result_set.cooperation`. Currently this sets all self interactions to be 0 but I think that's not the right way to display it (note that the cooperation rates were all being done correctly). **As well as ensuring the work done in series is reduced and the parallel workers also calculate the scores (which I think is more efficient)** this also seems to be faster: On this branch: ```python import axelrod as axl players = [s() for s in axl.strategies if s.classifier["memory_depth"] == 1] tournament = axl.Tournament(players, turns=200, repetitions=100) results = tournament.play(processes=4) ``` Took: 1min 49s ```python import axelrod as axl players = [s() for s in axl.short_run_time_strategies] tournament = axl.Tournament(players, turns=200, repetitions=20) results = tournament.play(processes=4) ``` Took: 21min 2s On current master; ```python import axelrod as axl players = [s() for s in axl.strategies if s.classifier["memory_depth"] == 1] tournament = axl.Tournament(players, turns=200, repetitions=100) results = tournament.play(processes=4) ``` Took: 2min 36s ```python import axelrod as axl players = [s() for s in axl.short_run_time_strategies] tournament = axl.Tournament(players, turns=200, repetitions=20) results = tournament.play(processes=4) ``` Took: 28min 8s **One final aspect to consider** (I think) is that adding `dask` as a dependency open up the potential to use it's `delayed` decorator to do all our parallel processing. This would have the benefit of being able to use a distributed scheduler that `dask` has. (We'd have to investigate if this actually works based on our parallelisation but at least the possibility is there). --- .gitignore | 11 +- axelrod/__init__.py | 2 +- axelrod/ecosystem.py | 8 +- axelrod/fingerprint.py | 40 +- axelrod/interaction_utils.py | 38 +- axelrod/plot.py | 16 +- axelrod/result_set.py | 1197 ++++++----------- axelrod/tests/unit/test_ecosystem.py | 6 +- axelrod/tests/unit/test_fingerprint.py | 51 +- axelrod/tests/unit/test_plot.py | 49 +- axelrod/tests/unit/test_resultset.py | 513 ++----- axelrod/tests/unit/test_tournament.py | 158 +-- axelrod/tournament.py | 253 ++-- .../reading_and_writing_interactions.rst | 14 - .../tutorials/advanced/tournament_results.rst | 32 +- .../further_topics/morality_metrics.rst | 2 +- .../further_topics/spatial_tournaments.rst | 32 +- docs/tutorials/getting_started/index.rst | 1 - .../getting_started/interactions.rst | 66 - .../summarising_tournaments.rst | 4 +- requirements.txt | 2 + test_outputs/expected_test_tournament.csv | 61 + .../expected_test_tournament_no_results.csv | 61 + test_outputs/test_results.csv | 19 + test_outputs/test_results_spatial.csv | 13 + test_outputs/test_results_spatial_three.csv | 25 + test_outputs/test_results_spatial_two.csv | 13 + 27 files changed, 1073 insertions(+), 1614 deletions(-) delete mode 100644 docs/tutorials/getting_started/interactions.rst create mode 100644 test_outputs/expected_test_tournament.csv create mode 100644 test_outputs/expected_test_tournament_no_results.csv create mode 100644 test_outputs/test_results.csv create mode 100644 test_outputs/test_results_spatial.csv create mode 100644 test_outputs/test_results_spatial_three.csv create mode 100644 test_outputs/test_results_spatial_two.csv diff --git a/.gitignore b/.gitignore index 6400ab3d7..0c3f3de6d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ cache.txt test.csv summary.csv basic_tournament.csv -test_outputs/*csv +test_outputs/*csv.summary test_outputs/*svg test_outputs/*cache @@ -118,3 +118,12 @@ docker-compose.yml # Mypy files .mypy_cache/ + +test_outputs/stochastic_tournament_0.csv +test_outputs/stochastic_tournament_1.csv +test_outputs/test_fingerprint.csv +test_outputs/test_fingerprint_tmp.csv +test_outputs/test_results_from_file.csv +test_outputs/test_results_from_file_tmp.csv +test_outputs/test_tournament.csv +test_outputs/tran_fin.csv diff --git a/axelrod/__init__.py b/axelrod/__init__.py index 009d09c55..599e2397c 100644 --- a/axelrod/__init__.py +++ b/axelrod/__init__.py @@ -18,7 +18,7 @@ from .deterministic_cache import DeterministicCache from .match_generator import * from .tournament import Tournament -from .result_set import ResultSet, ResultSetFromFile +from .result_set import ResultSet from .ecosystem import Ecosystem from .fingerprint import AshlockFingerprint, TransitiveFingerprint diff --git a/axelrod/ecosystem.py b/axelrod/ecosystem.py index f75cff485..61f4d81cb 100644 --- a/axelrod/ecosystem.py +++ b/axelrod/ecosystem.py @@ -12,7 +12,7 @@ def __init__(self, results: ResultSet, population: List[int] = None) -> None: self.results = results - self.nplayers = self.results.nplayers + self.num_players = self.results.num_players self.payoff_matrix = self.results.payoff_matrix self.payoff_stddevs = self.results.payoff_stddevs @@ -27,7 +27,7 @@ def __init__(self, results: ResultSet, if min(population) < 0: raise TypeError( "Minimum value of population vector must be non-negative") - elif len(population) != self.nplayers: + elif len(population) != self.num_players: raise TypeError( "Population vector must be same size as number of players") else: @@ -35,7 +35,7 @@ def __init__(self, results: ResultSet, self.population_sizes = [[p / norm for p in population]] else: self.population_sizes = [ - [1 / self.nplayers for _ in range(self.nplayers)]] + [1 / self.num_players for _ in range(self.num_players)]] # This function is quite arbitrary and probably only influences the # kinetics for the current code. @@ -47,7 +47,7 @@ def __init__(self, results: ResultSet, def reproduce(self, turns: int): for iturn in range(turns): - plist = list(range(self.nplayers)) + plist = list(range(self.num_players)) pops = self.population_sizes[-1] # The unit payoff for each player in this turn is the sum of the diff --git a/axelrod/fingerprint.py b/axelrod/fingerprint.py index 9cc9c6f54..b3518f956 100644 --- a/axelrod/fingerprint.py +++ b/axelrod/fingerprint.py @@ -6,6 +6,8 @@ import matplotlib.pyplot as plt import numpy as np import tqdm +import dask.dataframe as dd +import dask as da from mpl_toolkits.axes_grid1 import make_axes_locatable import axelrod as axl @@ -266,7 +268,7 @@ def construct_tournament_elements(self, step: float, def fingerprint( self, turns: int = 50, repetitions: int = 10, step: float = 0.01, - processes: int=None, filename: str = None, in_memory: bool = False, + processes: int=None, filename: str = None, progress_bar: bool = True ) -> dict: """Build and play the spatial tournament. @@ -290,10 +292,7 @@ def fingerprint( The number of processes to be used for parallel processing filename: str, optional The name of the file for self.spatial_tournament's interactions. - if None and in_memory=False, will auto-generate a filename. - in_memory: bool - Whether self.spatial_tournament keeps interactions_dict in memory or - in a file. + if None, will auto-generate a filename. progress_bar : bool Whether or not to create a progress bar which will be updated @@ -305,7 +304,7 @@ def fingerprint( """ temp_file_descriptor = None - if not in_memory and filename is None: + if filename is None: temp_file_descriptor, filename = mkstemp() edges, tourn_players = self.construct_tournament_elements( @@ -318,13 +317,10 @@ def fingerprint( self.spatial_tournament.play(build_results=False, filename=filename, processes=processes, - in_memory=in_memory, progress_bar=progress_bar) - if in_memory: - self.interactions = self.spatial_tournament.interactions_dict - else: - self.interactions = read_interactions_from_file( - filename, progress_bar=progress_bar) + + self.interactions = read_interactions_from_file( + filename, progress_bar=progress_bar) if temp_file_descriptor is not None: os.close(temp_file_descriptor) @@ -483,17 +479,19 @@ def analyse_cooperation_ratio(filename): opponent in each turn. The ith row corresponds to the ith opponent and the jth column the jth turn. """ - did_c = np.vectorize(lambda action: int(action == 'C')) + did_c = np.vectorize(lambda actions: [int(action == 'C') + for action in actions]) cooperation_rates = {} - with open(filename, "r") as f: - reader = csv.reader(f) - for row in reader: - opponent_index, player_history = int(row[1]), list(row[4]) - if opponent_index in cooperation_rates: - cooperation_rates[opponent_index].append(did_c(player_history)) - else: - cooperation_rates[opponent_index] = [did_c(player_history)] + df = dd.read_csv(filename) + df = df[df["Player index"] == 0][["Opponent index", "Actions"]] + + for _, row in df.iterrows(): + opponent_index, player_history = row["Opponent index"], row["Actions"] + if opponent_index in cooperation_rates: + cooperation_rates[opponent_index].append(did_c(player_history)) + else: + cooperation_rates[opponent_index] = [did_c(player_history)] for index, rates in cooperation_rates.items(): cooperation_rates[index] = np.mean(rates, axis=0) diff --git a/axelrod/interaction_utils.py b/axelrod/interaction_utils.py index 4338a162a..340058692 100644 --- a/axelrod/interaction_utils.py +++ b/axelrod/interaction_utils.py @@ -10,6 +10,7 @@ from collections import Counter import csv import tqdm +import pandas as pd from axelrod.action import Action, str_to_actions from .game import Game @@ -239,36 +240,27 @@ def compute_sparklines(interactions, c_symbol='█', d_symbol=' '): sparkline(histories[1], c_symbol, d_symbol)) -def read_interactions_from_file(filename, progress_bar=True, - num_interactions=False): +def read_interactions_from_file(filename, + progress_bar=True, + ): """ Reads a file and returns a dictionary mapping tuples of player pairs to lists of interactions """ + df = pd.read_csv(filename)[["Interaction index", "Player index", + "Opponent index", "Actions"]] + groupby = df.groupby("Interaction index") if progress_bar: - if not num_interactions: - with open(filename) as f: - num_interactions = sum(1 for line in f) - progress_bar = tqdm.tqdm(total=num_interactions, desc="Loading") + groupby = tqdm.tqdm(groupby) pairs_to_interactions = {} - with open(filename, 'r') as f: - for row in csv.reader(f): - index_pair = (int(row[0]), int(row[1])) - p1_actions = str_to_actions(row[4]) - p2_actions = str_to_actions(row[5]) - interaction = list(zip(p1_actions, p2_actions)) - - try: - pairs_to_interactions[index_pair].append(interaction) - except KeyError: - pairs_to_interactions[index_pair] = [interaction] - - if progress_bar: - progress_bar.update() - - if progress_bar: - progress_bar.close() + for _, d in tqdm.tqdm(groupby): + key = tuple(d[["Player index", "Opponent index"]].iloc[0]) + value = list(map(str_to_actions, zip(*d["Actions"]))) + try: + pairs_to_interactions[key].append(value) + except KeyError: + pairs_to_interactions[key] = [value] return pairs_to_interactions diff --git a/axelrod/plot.py b/axelrod/plot.py index a80381311..9f170bd02 100644 --- a/axelrod/plot.py +++ b/axelrod/plot.py @@ -25,7 +25,7 @@ def default_cmap(version: str = "2.0") -> str: class Plot(object): def __init__(self, result_set: ResultSet) -> None: self.result_set = result_set - self.nplayers = self.result_set.nplayers + self.num_players = self.result_set.num_players self.players = self.result_set.players def _violinplot( @@ -40,16 +40,16 @@ def _violinplot( ax = ax figure = ax.get_figure() - width = max(self.nplayers / 3, 12) + width = max(self.num_players / 3, 12) height = width / 2 spacing = 4 - positions = spacing * arange(1, self.nplayers + 1, 1) + positions = spacing * arange(1, self.num_players + 1, 1) figure.set_size_inches(width, height) ax.violinplot(data, positions=positions, widths=spacing / 2, showmedians=True, showextrema=False) ax.set_xticks(positions) ax.set_xticklabels(names, rotation=90) - ax.set_xlim([0, spacing * (self.nplayers + 1)]) + ax.set_xlim([0, spacing * (self.num_players + 1)]) ax.tick_params(axis='both', which='both', labelsize=8) if title: ax.set_title(title) @@ -175,14 +175,14 @@ def _payoff_heatmap( ax = ax figure = ax.get_figure() - width = max(self.nplayers / 4, 12) + width = max(self.num_players / 4, 12) height = width figure.set_size_inches(width, height) matplotlib_version = matplotlib.__version__ cmap = default_cmap(matplotlib_version) mat = ax.matshow(data, cmap=cmap) - ax.set_xticks(range(self.result_set.nplayers)) - ax.set_yticks(range(self.result_set.nplayers)) + ax.set_xticks(range(self.result_set.num_players)) + ax.set_yticks(range(self.result_set.num_players)) ax.set_xticklabels(names, rotation=90) ax.set_yticklabels(names) ax.tick_params(axis='both', which='both', labelsize=16) @@ -246,7 +246,7 @@ def stackplot( ticks = [] for i, n in enumerate(self.result_set.ranked_names): x = -0.01 - y = (i + 0.5) * 1 / self.result_set.nplayers + y = (i + 0.5) * 1 / self.result_set.num_players ax.annotate( n, xy=(x, y), xycoords=trans, clip_on=False, va='center', ha='right', fontsize=5) diff --git a/axelrod/result_set.py b/axelrod/result_set.py index 480af41af..810b02b32 100644 --- a/axelrod/result_set.py +++ b/axelrod/result_set.py @@ -1,9 +1,14 @@ from collections import namedtuple, Counter +from multiprocessing import cpu_count import csv +import itertools -from numpy import mean, nanmedian, std +from numpy import mean, nanmedian, std, array, nan_to_num import tqdm +import dask as da +import dask.dataframe as dd + from axelrod.action import Action, str_to_actions import axelrod.interaction_utils as iu from . import eigen @@ -28,314 +33,297 @@ def wrapper(*args): return wrapper -class ResultSet(object): - """A class to hold the results of a tournament.""" +class ResultSet(): + """ + A class to hold the results of a tournament. Reads in a CSV file produced + by the tournament class. + """ - def __init__(self, players, interactions, repetitions=False, - progress_bar=True, game=None): + def __init__(self, filename, + players, repetitions, + processes=None, progress_bar=True): """ Parameters ---------- + filename : string + the file from which to read the interactions players : list - a list of player objects. - interactions : dict - a dictionary mapping tuples of player indices to - interactions (1 for each repetition) + A list of the names of players. If not known will be efficiently + read from file. repetitions : int - The number of repetitions - game : axlerod.game - The particular game used. - progress_bar : bool - Whether or not to create a progress bar which will be updated - """ - if game is None: - self.game = Game() - else: - self.game = game - - self.interactions = interactions - self.players = players - self.num_matches = len(interactions) - - if not players or not repetitions: - self.players, self.repetitions = self._read_players_and_repetition_numbers(progress_bar=progress_bar) - else: - self.players, self.repetitions = players, repetitions - - self.nplayers = len(self.players) - - self._build_empty_metrics() - self._build_score_related_metrics(progress_bar=progress_bar) - - def create_progress_bar(self, desc=None): - """ - Create a progress bar for a read through of the data file. - - Parameters - ---------- - desc : string - A description. + The number of repetitions of each match. If not know will be + efficiently read from file. + processes : integer + The number of processes to be used for parallel processing """ - return tqdm.tqdm(total=self.num_matches, desc=desc) + self.filename = filename + self.players, self.repetitions = players, repetitions + self.num_players = len(self.players) - def _update_players(self, index_pair, players): - """ - During a read of the data, update the internal players dictionary + if progress_bar: + self.progress_bar = tqdm.tqdm(total=25, + desc="Analysing") - Parameters - ---------- + df = dd.read_csv(filename) + dask_tasks = self._build_tasks(df) - index_pair : tuple - A tuple of player indices - players : tuple - A tuple of player names - """ - for index, player in zip(index_pair, players): - if index not in self.players_d: - self.players_d[index] = player + if processes == 0: + processes = cpu_count() - def _update_repetitions(self, index_pair, nbr=1): - """ - During a read of the data, update the internal repetitions dictionary + out = self._compute_tasks(tasks=dask_tasks, processes=processes) - Parameters - ---------- + self._reshape_out(out) - index_pair : tuple - A tuple of player indices - nbr : integer - The number of repetitions - """ - try: - self.repetitions_d[index_pair] += nbr - except KeyError: - self.repetitions_d[index_pair] = nbr + if progress_bar: + self.progress_bar.close() - def _build_repetitions(self): + def _reshape_out(self, out): """ - Count the number of repetitions - - Returns - ------- - - repetitions : int - The number of repetitions + Reshape the various pandas series objects to be of the required form and + set the corresponding attributes. """ - repetitions = max(self.repetitions_d.values()) - del self.repetitions_d # Manual garbage collection - return repetitions + (mean_per_reps_player_opponent_df, + sum_per_player_opponent_df, + sum_per_player_repetition_df, + normalised_scores_series, + initial_cooperation_count_series, + interactions_count_series) = out - def _build_players(self): - """ - List the players - - Returns - ------- + self.payoffs = self._build_payoffs(mean_per_reps_player_opponent_df["Score per turn"]) + self.score_diffs = self._build_score_diffs(mean_per_reps_player_opponent_df["Score difference per turn"]) + self.match_lengths = self._build_match_lengths(mean_per_reps_player_opponent_df["Turns"]) - players : list - An ordered list of players - """ - players = [] - for i in range(len(self.players_d)): - players.append(self.players_d[i]) + self.wins = self._build_wins(sum_per_player_repetition_df["Win"]) + self.scores = self._build_scores(sum_per_player_repetition_df["Score"]) + self.normalised_scores = self._build_normalised_scores(normalised_scores_series) + self.cooperation = self._build_cooperation(sum_per_player_opponent_df["Cooperation count"]) + self.good_partner_matrix = self._build_good_partner_matrix(sum_per_player_opponent_df["Good partner"]) - del self.players_d # Manual garbage collection - return players + columns = ["CC count", "CD count", "DC count", "DD count"] + self.state_distribution = self._build_state_distribution(sum_per_player_opponent_df[columns]) + self.normalised_state_distribution = self._build_normalised_state_distribution() - @update_progress_bar - def _build_eigenmoses_rating(self): - """ - Returns: - -------- + columns = ["CC to C count", + "CC to D count", + "CD to C count", + "CD to D count", + "DC to C count", + "DC to D count", + "DD to C count", + "DD to D count"] + self.state_to_action_distribution = self._build_state_to_action_distribution(sum_per_player_opponent_df[columns]) + self.normalised_state_to_action_distribution = self._build_normalised_state_to_action_distribution() - The eigenmoses rating as defined in: - http://www.scottaaronson.com/morality.pdf - """ - eigenvector, eigenvalue = eigen.principal_eigenvector( - self.vengeful_cooperation) + self.initial_cooperation_count = self._build_initial_cooperation_count(initial_cooperation_count_series) + self.initial_cooperation_rate = self._build_initial_cooperation_rate(interactions_count_series) + self.good_partner_rating = self._build_good_partner_rating(interactions_count_series) - return eigenvector.tolist() + self.normalised_cooperation = self._build_normalised_cooperation() + self.ranking = self._build_ranking() + self.ranked_names = self._build_ranked_names() + self.payoff_matrix = self._build_payoff_matrix() + self.payoff_stddevs = self._build_payoff_stddevs() + self.payoff_diffs_means = self._build_payoff_diffs_means() + self.cooperating_rating = self._build_cooperating_rating() + self.vengeful_cooperation = self._build_vengeful_cooperation() + self.eigenjesus_rating = self._build_eigenjesus_rating() + self.eigenmoses_rating = self._build_eigenmoses_rating() @update_progress_bar - def _build_eigenjesus_rating(self): - """ - Returns: - -------- - - The eigenjesus rating as defined in: - http://www.scottaaronson.com/morality.pdf + def _build_payoffs(self, payoffs_series): """ - eigenvector, eigenvalue = eigen.principal_eigenvector( - self.normalised_cooperation) + Parameters + ---------- - return eigenvector.tolist() + payoffs_series : pandas.Series - @update_progress_bar - def _build_cooperating_rating(self): - """ Returns: -------- - - The list of cooperation ratings + 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: - [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. + 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)) - total_length_v_opponent = [zip(*[rep[player_index] for - rep in self.match_lengths]) - for player_index 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, sum(ls)) for cs, ls - in zip(self.cooperation, lengths)] + payoffs_dict = dict(payoffs_series) + payoffs = [] + for player_index in range(self.num_players): + matrix = [] + for opponent_index in range(self.num_players): + row = [] + for repetition in range(self.repetitions): + key = (repetition, player_index, opponent_index) + if key in payoffs_dict: + row.append(payoffs_dict[key]) + matrix.append(row) + payoffs.append(matrix) + return payoffs @update_progress_bar - def _build_vengeful_cooperation(self): + def _build_score_diffs(self, payoff_diffs_series): """ - Returns: - -------- + Parameters + ---------- - The vengeful cooperation matrix derived from the - normalised cooperation matrix: + payoffs_diffs_series : pandas.Series - Dij = 2(Cij - 0.5) - """ - return [[2 * (element - 0.5) for element in row] - for row in self.normalised_cooperation] - - @update_progress_bar - def _build_payoff_diffs_means(self): - """ Returns: -------- - - The score differences between players. + The mean of per turn payoff differences 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. - """ - payoff_diffs_means = [[mean(diff) for diff in player] - for player in self.score_diffs] - return payoff_diffs_means - - @update_progress_bar - def _build_payoff_stddevs(self): + 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 + difference (over all repetitions) obtained by player i against + player j. """ - 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: + payoff_diffs_dict = payoff_diffs_series.to_dict() + score_diffs = [] + for player_index in range(self.num_players): + matrix = [] + for opponent_index in range(self.num_players): + row = [] + for repetition in range(self.repetitions): + row.append(payoff_diffs_dict.get((repetition, + player_index, + opponent_index, + ), 0)) + matrix.append(row) + score_diffs.append(matrix) + return score_diffs - [pi1, pi2, pi3, ..., pim] + @update_progress_bar + def _build_match_lengths(self, length_series): + length_dict = dict(length_series) + match_lengths = [] + for repetition in range(self.repetitions): + matrix = [] + for player_index in range(self.num_players): + row = [] + for opponent_index in range(self.num_players): + row.append(length_dict.get((repetition, + player_index, + opponent_index), 0)) + matrix.append(row) + match_lengths.append(matrix) + return match_lengths - Where m is the number of players and pij is a list of the form: + @update_progress_bar + def _build_wins(self, wins_series): + wins_dict = wins_series.to_dict() + wins = [[wins_dict.get((player_index, repetition), 0) + for repetition in range(self.repetitions)] + for player_index in range(self.num_players)] + return wins - [uij1, uij2, ..., uijk] + @update_progress_bar + def _build_scores(self, scores_series): + scores_dict = scores_series.to_dict() + scores = [[scores_series.get((player_index, repetition), 0) + for repetition in range(self.repetitions)] + for player_index in range(self.num_players)] + return scores - 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 opponent in plist] for player in plist] + @update_progress_bar + def _build_normalised_scores(self, normalised_scores_series): + normalised_scores_dict = normalised_scores_series.to_dict() + normalised_scores = [[normalised_scores_series.get((player_index, + repetition), 0) + for repetition in range(self.repetitions)] + for player_index in range(self.num_players)] + return normalised_scores - for player in plist: - for opponent in plist: - utilities = self.payoffs[player][opponent] + @update_progress_bar + def _build_cooperation(self, cooperation_series): + cooperation_dict = cooperation_series.to_dict() + cooperation = [] + for player_index in range(self.num_players): + row = [] + for opponent_index in range(self.num_players): + count = cooperation_dict.get((player_index, opponent_index),0) + if player_index == opponent_index: + # Address double count + count = int(count / 2) + row.append(count) + cooperation.append(row) + return cooperation - if utilities: - payoff_stddevs[player][opponent] = std(utilities) + @update_progress_bar + def _build_good_partner_matrix(self, good_partner_series): + good_partner_dict = dict(good_partner_series) + good_partner_matrix = [] + for player_index in range(self.num_players): + row = [] + for opponent_index in range(self.num_players): + if player_index == opponent_index: + # The reduce operation implies a double count of self + # interactions. + row.append(0) else: - payoff_stddevs[player][opponent] = 0 + row.append(good_partner_dict.get((player_index, + opponent_index), 0)) + good_partner_matrix.append(row) + return good_partner_matrix - return payoff_stddevs @update_progress_bar def _build_payoff_matrix(self): - """ - Returns: - -------- - The mean of per turn payoffs. - List of the form: + payoff_matrix = [[0 for opponent_index in range(self.num_players)] + for player_index in range(self.num_players)] - [ML1, ML2, ML3..., MLn] + pairs = itertools.product(range(self.num_players), repeat=2) - Where n is the number of players and MLi is a list of the form: + for player_index, opponent_index in pairs: + utilities = self.payoffs[player_index][opponent_index] + if utilities: + payoff_matrix[player_index][opponent_index] = mean(utilities) - [pi1, pi2, pi3, ..., pim] + return payoff_matrix - Where m is the number of players and pij is a list of the form: + @update_progress_bar + def _build_payoff_stddevs(self): + payoff_stddevs = [[0 for opponent_index in range(self.num_players)] + for player_index in range(self.num_players)] - [uij1, uij2, ..., uijk] + pairs = itertools.product(range(self.num_players), repeat=2) - 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 = [[[] for opponent in plist] for player in plist] - - for player in plist: - for opponent in plist: - utilities = self.payoffs[player][opponent] + for player_index, opponent_index in pairs: + utilities = self.payoffs[player_index][opponent_index] + if utilities: + payoff_stddevs[player_index][opponent_index] = std(utilities) - if utilities: - payoff_matrix[player][opponent] = mean(utilities) - else: - payoff_matrix[player][opponent] = 0 + return payoff_stddevs - return payoff_matrix @update_progress_bar - def _build_ranked_names(self): - """ - Returns: - -------- - Returns the ranked names. A list of names as calculated by - self.ranking. - """ + def _build_payoff_diffs_means(self): + payoff_diffs_means = [[mean(diff) for diff in player] + for player in self.score_diffs] - return [str(self.players[i]) for i in self.ranking] + return payoff_diffs_means @update_progress_bar - 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: -nanmedian(self.normalised_scores[i])) + def _build_state_distribution(self, state_distribution_series): + state_key_map = {'CC count': (C, C), + 'CD count': (C, D), + 'DC count': (D, C), + 'DD count': (D, D)} + state_distribution = [[create_counter_dict(state_distribution_series, + player_index, + opponent_index, + state_key_map) + for opponent_index in range(self.num_players)] + for player_index in range(self.num_players)] + return state_distribution @update_progress_bar def _build_normalised_state_distribution(self): @@ -349,15 +337,35 @@ def _build_normalised_state_distribution(self): Dictionary where the keys are the states and the values are a normalized counts of the number of times that state occurs. """ - norm = [] + normalised_state_distribution = [] for player in self.state_distribution: counters = [] for counter in player: total = sum(counter.values()) counters.append(Counter({key: value / total for key, value in counter.items()})) - norm.append(counters) - return norm + normalised_state_distribution.append(counters) + return normalised_state_distribution + + @update_progress_bar + def _build_state_to_action_distribution(self, + state_to_action_distribution_series): + state_to_action_key_map = {"CC to C count": ((C, C), C), + "CC to D count": ((C, C), D), + "CD to C count": ((C, D), C), + "CD to D count": ((C, D), D), + "DC to C count": ((D, C), C), + "DC to D count": ((D, C), D), + "DD to C count": ((D, D), C), + "DD to D count": ((D, D), D)} + state_to_action_distribution = [[ + create_counter_dict(state_to_action_distribution_series, + player_index, + opponent_index, + state_to_action_key_map) + for opponent_index in range(self.num_players)] + for player_index in range(self.num_players)] + return state_to_action_distribution @update_progress_bar def _build_normalised_state_to_action_distribution(self): @@ -372,7 +380,7 @@ def _build_normalised_state_to_action_distribution(self): normalized counts of the number of times that state goes to a given action. """ - norm = [] + normalised_state_to_action_distribution = [] for player in self.state_to_action_distribution: counters = [] for counter in player: @@ -384,385 +392,191 @@ def _build_normalised_state_to_action_distribution(self): if counter[(state, action)] > 0: norm_counter[(state, action)] = counter[(state, action)] / total counters.append(norm_counter) - norm.append(counters) - return norm - - def _build_empty_metrics(self, keep_interactions=False): - """ - Creates the various empty metrics ready to be updated as the data is - read. - - Parameters - ---------- - - keep_interactions : bool - Whether or not to load the interactions in to memory - """ - plist = range(self.nplayers) - replist = range(self.repetitions) - self.match_lengths = [[[0 for opponent in plist] for player in plist] - for _ in replist] - self.wins = [[0 for _ in replist] for player in plist] - self.scores = [[0 for _ in replist] for player in plist] - self.normalised_scores = [[[] for _ in replist] for player in plist] - self.payoffs = [[[] for opponent in plist] for player in plist] - self.score_diffs = [[[0] * self.repetitions for opponent in plist] - for player in plist] - self.cooperation = [[0 for opponent in plist] for player in plist] - self.normalised_cooperation = [[[] for opponent in plist] - for player in plist] - self.initial_cooperation_count = [0 for player in plist] - self.state_distribution = [[Counter() for opponent in plist] - for player in plist] - self.state_to_action_distribution = [[Counter() for opponent in plist] - for player in plist] - self.good_partner_matrix = [[0 for opponent in plist] - for player in plist] - - self.total_interactions = [0 for player in plist] - self.good_partner_rating = [0 for player in plist] - - if keep_interactions: - self.interactions = {} - - def _update_match_lengths(self, repetition, p1, p2, interaction): - """ - During a read of the data, update the match lengths attribute - - Parameters - ---------- - - repetition : int - The index of the repetition - p1, p2 : int - The indices of the first and second player - interaction : list - A list of tuples of interactions - """ - self.match_lengths[repetition][p1][p2] = len(interaction) - - def _update_payoffs(self, p1, p2, scores_per_turn): - """ - During a read of the data, update the payoffs attribute - - Parameters - ---------- - - p1, p2 : int - The indices of the first and second player - scores_per_turn : tuples - A 2-tuple of the scores per turn for a given match - """ - self.payoffs[p1][p2].append(scores_per_turn[0]) - if p1 != p2: - self.payoffs[p2][p1].append(scores_per_turn[1]) - - def _update_score_diffs(self, repetition, p1, p2, scores_per_turn): - """ - During a read of the data, update the score diffs attribute - - Parameters - ---------- - - p1, p2 : int - The indices of the first and second player - scores_per_turn : tuples - A 2-tuple of the scores per turn for a given match - """ - diff = scores_per_turn[0] - scores_per_turn[1] - self.score_diffs[p1][p2][repetition] = diff - self.score_diffs[p2][p1][repetition] = -diff - - def _update_normalised_cooperation(self, p1, p2, interaction): - """ - During a read of the data, update the normalised cooperation attribute - - Parameters - ---------- - - p1, p2 : int - The indices of the first and second player - interaction : list of tuples - A list of interactions - """ - normalised_cooperations = iu.compute_normalised_cooperation(interaction) + normalised_state_to_action_distribution.append(counters) + return normalised_state_to_action_distribution - self.normalised_cooperation[p1][p2].append(normalised_cooperations[0]) - self.normalised_cooperation[p2][p1].append(normalised_cooperations[1]) - - def _update_wins(self, repetition, p1, p2, interaction): - """ - During a read of the data, update the wins attribute - - Parameters - ---------- - - repetition : int - The index of a repetition - p1, p2 : int - The indices of the first and second player - interaction : list of tuples - A list of interactions - """ - match_winner_index = iu.compute_winner_index(interaction, - game=self.game) - index_pair = [p1, p2] - if match_winner_index is not False: - winner_index = index_pair[match_winner_index] - self.wins[winner_index][repetition] += 1 - - def _update_scores(self, repetition, p1, p2, interaction): - """ - During a read of the data, update the scores attribute + @update_progress_bar + def _build_initial_cooperation_count(self, initial_cooperation_count_series): + initial_cooperation_count_dict = initial_cooperation_count_series.to_dict() + initial_cooperation_count = [ + initial_cooperation_count_dict.get(player_index, 0) + for player_index in + range(self.num_players)] + return initial_cooperation_count - Parameters - ---------- + @update_progress_bar + def _build_normalised_cooperation(self): + normalised_cooperation = [list(nan_to_num(row)) + for row in array(self.cooperation) / + sum(map(array, self.match_lengths))] + return normalised_cooperation - repetition : int - The index of a repetition - p1, p2 : int - The indices of the first and second player - interaction : list of tuples - A list of interactions - """ - final_scores = iu.compute_final_score(interaction, game=self.game) - for index, player in enumerate([p1, p2]): - player_score = final_scores[index] - self.scores[player][repetition] += player_score + @update_progress_bar + def _build_initial_cooperation_rate(self, interactions_series): + interactions_dict = interactions_series.to_dict() + interactions_array = array([interactions_series.get(player_index, 0) + for player_index in range(self.num_players)]) + initial_cooperation_rate = list( + nan_to_num(array(self.initial_cooperation_count) / + interactions_array)) + return initial_cooperation_rate - def _update_normalised_scores(self, repetition, p1, p2, scores_per_turn): - """ - During a read of the data, update the normalised scores attribute + @update_progress_bar + def _build_ranking(self): + ranking = sorted( + range(self.num_players), + key=lambda i: -nanmedian(self.normalised_scores[i])) + return ranking - Parameters - ---------- + @update_progress_bar + def _build_ranked_names(self): + ranked_names = [str(self.players[i]) for i in self.ranking] + return ranked_names - repetition : int - The index of a repetition - p1, p2 : int - The indices of the first and second player - scores_per_turn : tuple - A 2 tuple with the scores per turn of each player + @update_progress_bar + def _build_eigenmoses_rating(self): """ - for index, player in enumerate([p1, p2]): - score_per_turn = scores_per_turn[index] - self.normalised_scores[player][repetition].append(score_per_turn) + Returns: + -------- - def _update_cooperation(self, p1, p2, cooperations): + The eigenmoses rating as defined in: + http://www.scottaaronson.com/morality.pdf """ - During a read of the data, update the cooperation attribute + eigenvector, eigenvalue = eigen.principal_eigenvector( + self.vengeful_cooperation) - Parameters - ---------- + return eigenvector.tolist() - p1, p2 : int - The indices of the first and second player - cooperations : tuple - A 2 tuple with the count of cooperation each player + @update_progress_bar + def _build_eigenjesus_rating(self): """ - self.cooperation[p1][p2] += cooperations[0] - self.cooperation[p2][p1] += cooperations[1] + Returns: + -------- - def _update_initial_cooperation_count(self, p1, p2, initial_cooperations): + The eigenjesus rating as defined in: + http://www.scottaaronson.com/morality.pdf """ - During a read of the data, update the initial cooperation count - attribute - - Parameters - ---------- + eigenvector, eigenvalue = eigen.principal_eigenvector( + self.normalised_cooperation) - p1, p2 : int - The indices of the first and second player - initial_cooperations : tuple - A 2 tuple with a 0 or 1 indicating if the initial move of each - player was Cooperation (0) or Defection (1), e.g. (0, 1) for a - round (C, D) - """ - self.initial_cooperation_count[p1] += initial_cooperations[0] - self.initial_cooperation_count[p2] += initial_cooperations[1] + return eigenvector.tolist() - def _update_state_distribution(self, p1, p2, counter): + @update_progress_bar + def _build_cooperating_rating(self): """ - During a read of the data, update the state_distribution attribute - - Parameters - ---------- + Returns: + -------- - p1, p2 : int - The indices of the first and second player - counter : collections.Counter - A counter object for the states of a match - """ - self.state_distribution[p1][p2] += counter + The list of cooperation ratings + List of the form: - counter[(C, D)], counter[(D, C)] = counter[(D, C)], counter[(C, D)] - self.state_distribution[p2][p1] += counter + [ML1, ML2, ML3..., MLn] - def _update_state_to_action_distribution(self, p1, p2, counter_list): - """ - During a read of the data, update the state_distribution attribute + Where n is the number of players and MLi is a list of the form: - Parameters - ---------- + [pi1, pi2, pi3, ..., pim] - p1, p2 : int - The indices of the first and second player - counter_list : list of collections.Counter - A list of counter objects for the states to action of a match + 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. """ - counter = counter_list[0] - self.state_to_action_distribution[p1][p2] += counter - counter = counter_list[1] - for act in [C, D]: - counter[((C, D), act)], counter[((D, C), act)] = counter[((D, C), act)], counter[((C, D), act)] - self.state_to_action_distribution[p2][p1] += counter - - def _update_good_partner_matrix(self, p1, p2, cooperations): - """ - During a read of the data, update the good partner matrix attribute + plist = list(range(self.num_players)) + total_length_v_opponent = [zip(*[rep[player_index] for + rep in self.match_lengths]) + for player_index in plist] + lengths = [[sum(e) for j, e in enumerate(row) if i != j] for i, row in + enumerate(total_length_v_opponent)] - Parameters - ---------- + cooperation = [[col for j, col in enumerate(row) if i != j] + for i, row in enumerate(self.cooperation)] + # Max is to deal with edge cases of matches that have no turns + cooperating_rating = [sum(cs) / max(1, sum(ls)) + for cs, ls in zip(cooperation, lengths)] + return cooperating_rating - p1, p2 : int - The indices of the first and second player - cooperations : tuple - A 2 tuple with the count of cooperation each player + @update_progress_bar + def _build_vengeful_cooperation(self): """ - if cooperations[0] >= cooperations[1]: - self.good_partner_matrix[p1][p2] += 1 - if cooperations[1] >= cooperations[0]: - self.good_partner_matrix[p2][p1] += 1 + Returns: + -------- - def _summarise_normalised_scores(self): - """ - At the end of a read of the data, finalise the normalised scores - """ - for i, rep in enumerate(self.normalised_scores): - for j, player_scores in enumerate(rep): - if player_scores != []: - self.normalised_scores[i][j] = mean(player_scores) - else: - self.normalised_scores[i][j] = 0 - try: - self.progress_bar.update() - except AttributeError: - pass + The vengeful cooperation matrix derived from the + normalised cooperation matrix: - def _summarise_normalised_cooperation(self): - """ - At the end of a read of the data, finalise the normalised cooperation + Dij = 2(Cij - 0.5) """ - for i, rep in enumerate(self.normalised_cooperation): - for j, cooperation in enumerate(rep): - if cooperation != []: - self.normalised_cooperation[i][j] = mean(cooperation) - else: - self.normalised_cooperation[i][j] = 0 - try: - self.progress_bar.update() - except AttributeError: - pass + vengeful_cooperation = [[2 * (element - 0.5) for element in row] + for row in self.normalised_cooperation] + return vengeful_cooperation @update_progress_bar - def _build_good_partner_rating(self): + def _build_good_partner_rating(self, interactions_series): """ At the end of a read of the data, build the good partner rating attribute """ - return [sum(self.good_partner_matrix[player]) / - max(1, self.total_interactions[player]) - for player in range(self.nplayers)] + interactions_dict = interactions_series.to_dict() + good_partner_rating = [sum(self.good_partner_matrix[player]) / + max(1, interactions_dict.get(player, 0)) + for player in range(self.num_players)] + return good_partner_rating - @update_progress_bar - def _build_initial_cooperation_rate(self): + def _compute_tasks(self, tasks, processes): """ - At the end of a read of the data, build the normalised initial - cooperation rate attribute + Compute all dask tasks """ - return [self.initial_cooperation_count[player] / - max(1, self.total_interactions[player]) - for player in range(self.nplayers)] - - def _build_score_related_metrics(self, progress_bar=False, - keep_interactions=False): - """ - Read the data and carry out all relevant calculations. - - Parameters - ---------- - progress_bar : bool - Whether or not to display a progress bar - keep_interactions : bool - Whether or not to lad the interactions in to memory - """ - match_chunks = self.read_match_chunks(progress_bar) - - for match in match_chunks: - p1, p2 = int(match[0][0]), int(match[0][1]) - - for repetition, record in enumerate(match): - interaction = record[4:] - - if keep_interactions: - try: - self.interactions[(p1, p2)].append(interaction) - except KeyError: - self.interactions[(p1, p2)] = [interaction] - - scores_per_turn = iu.compute_final_score_per_turn(interaction, - game=self.game) - cooperations = iu.compute_cooperations(interaction) - state_counter = iu.compute_state_distribution(interaction) - - self._update_match_lengths(repetition, p1, p2, interaction) - self._update_payoffs(p1, p2, scores_per_turn) - self._update_score_diffs(repetition, p1, p2, scores_per_turn) - self._update_normalised_cooperation(p1, p2, interaction) - - if p1 != p2: # Anything that ignores self interactions - state_to_actions = iu.compute_state_to_action_distribution(interaction) - - for player in [p1, p2]: - self.total_interactions[player] += 1 - - self._update_match_lengths(repetition, p2, p1, interaction) - self._update_wins(repetition, p1, p2, interaction) - self._update_scores(repetition, p1, p2, interaction) - self._update_normalised_scores(repetition, p1, p2, - scores_per_turn) - self._update_cooperation(p1, p2, cooperations) - initial_coops = iu.compute_cooperations(interaction[:1]) - self._update_initial_cooperation_count(p1, p2, - initial_coops) - self._update_state_distribution(p1, p2, state_counter) - self._update_state_to_action_distribution(p1, p2, - state_to_actions) - self._update_good_partner_matrix(p1, p2, cooperations) - - if progress_bar: - self.progress_bar = tqdm.tqdm(total=13 + 2 * self.nplayers, - desc="Finishing") - self._summarise_normalised_scores() - self._summarise_normalised_cooperation() - - self.ranking = self._build_ranking() - self.normalised_state_distribution = self._build_normalised_state_distribution() - self.normalised_state_to_action_distribution = self._build_normalised_state_to_action_distribution() - self.ranked_names = self._build_ranked_names() - self.payoff_matrix = self._build_payoff_matrix() - self.payoff_stddevs = self._build_payoff_stddevs() - self.payoff_diffs_means = self._build_payoff_diffs_means() - self.vengeful_cooperation = self._build_vengeful_cooperation() - self.cooperating_rating = self._build_cooperating_rating() - self.initial_cooperation_rate = self._build_initial_cooperation_rate() - self.good_partner_rating = self._build_good_partner_rating() - self.eigenjesus_rating = self._build_eigenjesus_rating() - self.eigenmoses_rating = self._build_eigenmoses_rating() - - if progress_bar: - self.progress_bar.close() + if processes is None: + out = da.compute(*tasks, get=da.get) + else: + out = da.compute(*tasks, num_workers=processes) + return out + + def _build_tasks(self, df): + """ + Returns a tuple of dask tasks + """ + groups = ["Repetition", "Player index", "Opponent index"] + columns = ["Turns", "Score per turn", "Score difference per turn"] + mean_per_reps_player_opponent_task = df.groupby(groups)[columns].mean() + + groups = ["Player index", "Opponent index"] + columns = ["Cooperation count", + "CC count", + "CD count", + "DC count", + "DD count", + "CC to C count", + "CC to D count", + "CD to C count", + "CD to D count", + "DC to C count", + "DC to D count", + "DD to C count", + "DD to D count", + "Good partner"] + sum_per_player_opponent_task = df.groupby(groups)[columns].sum() + + ignore_self_interactions_task = df["Player index"] != df["Opponent index"] + adf = df[ignore_self_interactions_task] + + groups = ["Player index", "Repetition"] + columns = ["Win", "Score"] + sum_per_player_repetition_task = adf.groupby(groups)[columns].sum() + + normalised_scores_task = adf.groupby(["Player index", + "Repetition"] + )["Score per turn"].mean() + initial_cooperation_count_task = adf.groupby(["Player index"])["Initial cooperation"].sum() + interactions_count_task = adf.groupby("Player index")["Player index"].count() + + + return (mean_per_reps_player_opponent_task, + sum_per_player_opponent_task, + sum_per_player_repetition_task, + normalised_scores_task, + initial_cooperation_count_task, + interactions_count_task) def __eq__(self, other): """ @@ -888,204 +702,29 @@ def write_summary(self, filename): for player in summary_data: writer.writerow(player) - def read_match_chunks(self, progress_bar=False): - """ - A generator to return a given repetitions of matches - - Parameters - ---------- - - progress_bar : bool - whether or not to display a progress bar - - Yields - ------ - repetitions : list - A list of lists include index pairs, player pairs and - repetitions. All repetitions for a given pair are yielded - together. - """ - - if progress_bar: - progress_bar = self.create_progress_bar(desc="Analysing") - - for match_pair, interactions in self.interactions.items(): - players_pair = [self.players[i] for i in match_pair] - repetitions = [list(match_pair) + players_pair + rep for rep in - interactions] - if progress_bar: - progress_bar.update() - yield repetitions - - if progress_bar: - progress_bar.close() - - def _read_players_and_repetition_numbers(self, progress_bar=False): - """ - Read the players and the repetitions numbers - - Parameters - ---------- - progress_bar : bool - Whether or not to display a progress bar - """ - if progress_bar: - progress_bar = self.create_progress_bar(desc="Counting") - - self.players_d = {} - self.repetitions_d = {} - for index_pair, interactions in self.interactions.items(): - players = [self.players[i] for i in index_pair] - self._update_repetitions(index_pair, len(interactions)) - self._update_players(index_pair, players) - if progress_bar: - progress_bar.update() - - if progress_bar: - progress_bar.close() - - repetitions = self._build_repetitions() - players = self._build_players() - - return players, repetitions - - -class ResultSetFromFile(ResultSet): +def create_counter_dict(df, player_index, opponent_index, key_map): """ - A class to hold the results of a tournament. Reads in a CSV file produced - by the tournament class. + Create a Counter object mapping states (corresponding to columns of df) for + players given by player_index, opponent_index. Renaming the variables with + `key_map`. Used by `ResultSet._reshape_out` + + Parameters + ---------- + df : a multiindex pandas df + player_index: int + opponent_index: int + key_map : a dict + maps cols of df to strings + + Returns + ------- + A counter dictionary """ - - def __init__(self, filename, progress_bar=True, - num_interactions=False, players=False, repetitions=False, - game=None, keep_interactions=False): - """ - Parameters - ---------- - filename : string - the file from which to read the interactions - progress_bar : bool - Whether or not to create a progress bar which will be updated - num_interactions : int - The number of interactions in the file. Used for the progress - bar. If not known but progress_bar is true, will be efficiently - read from file. - players : list - A list of the names of players. If not known will be efficiently - read from file. - repetitions : int - The number of repetitions of each match. If not know will be - efficiently read from file. - game : axelrod.Game - The particular game that should be used to calculate the scores. - keep_interactions : bool - Whether or not to load the interactions in to memory. WARNING: - for large tournaments this drastically increases the memory - required. - """ - if game is None: - self.game = Game() - else: - self.game = game - - self.filename = filename - self.num_interactions = num_interactions - - if not players and not repetitions: - self.players, self.repetitions = self._read_players_and_repetition_numbers(progress_bar=progress_bar) - else: - self.players, self.repetitions = players, repetitions - self.nplayers = len(self.players) - - self._build_empty_metrics(keep_interactions=keep_interactions) - self._build_score_related_metrics(progress_bar=progress_bar, - keep_interactions=keep_interactions) - - def create_progress_bar(self, desc=None): - """ - Create a progress bar for a read through of the data file. - - Parameters - ---------- - desc : string - A description. - """ - if not self.num_interactions: - with open(self.filename) as f: - self.num_interactions = sum(1 for line in f) - return tqdm.tqdm(total=self.num_interactions, desc=desc) - - def _read_players_and_repetition_numbers(self, progress_bar=False): - """ - Read the players and the repetitions numbers - - Parameters - ---------- - progress_bar : bool - Whether or not to display a progress bar - """ - - if progress_bar: - progress_bar = self.create_progress_bar(desc="Counting") - - self.players_d = {} - self.repetitions_d = {} - with open(self.filename, 'r') as f: - for row in csv.reader(f): - index_pair = (int(row[0]), int(row[1])) - players = (row[2], row[3]) - self._update_repetitions(index_pair) - self._update_players(index_pair, players) - if progress_bar: - progress_bar.update() - - if progress_bar: - progress_bar.close() - - repetitions = self._build_repetitions() - players = self._build_players() - - return players, repetitions - - def read_match_chunks(self, progress_bar=False): - """ - A generator to return a given repetitions of matches - - Parameters - ---------- - - progress_bar : bool - whether or not to display a progress bar - - Yields - ------ - repetitions : list - A list of lists include index pairs, player pairs and - repetitions. All repetitions for a given pair are yielded - together. - """ - - if progress_bar: - progress_bar = self.create_progress_bar(desc="Analysing") - - with open(self.filename, 'r') as f: - csv_reader = csv.reader(f) - repetitions = [] - count = 0 - for row in csv_reader: - index_and_names = row[:4] - p1_actions = str_to_actions(row[4]) - p2_actions = str_to_actions(row[5]) - interactions = list(zip(p1_actions, p2_actions)) - repetitions.append(index_and_names + interactions) - count += 1 - if progress_bar: - progress_bar.update() - if count == self.repetitions: - yield repetitions - repetitions = [] - count = 0 - - if progress_bar: - progress_bar.close() + counter = Counter() + if player_index != opponent_index: + if (player_index, opponent_index) in df.index: + for key, value in df.loc[player_index, opponent_index].items(): + if value > 0: + counter[key_map[key]] = value + return counter diff --git a/axelrod/tests/unit/test_ecosystem.py b/axelrod/tests/unit/test_ecosystem.py index 11626ee4d..67536f830 100644 --- a/axelrod/tests/unit/test_ecosystem.py +++ b/axelrod/tests/unit/test_ecosystem.py @@ -30,7 +30,7 @@ def test_init(self): # By default create populations of equal size eco = axelrod.Ecosystem(self.res_cooperators) pops = eco.population_sizes - self.assertEqual(eco.nplayers, 4) + self.assertEqual(eco.num_players, 4) self.assertEqual(len(pops), 1) self.assertEqual(len(pops[0]), 4) self.assertAlmostEqual(sum(pops[0]), 1.0) @@ -39,7 +39,7 @@ def test_init(self): # Can pass list of initial population distributions eco = axelrod.Ecosystem(self.res_cooperators, population=[.7, .25, .03, .02]) pops = eco.population_sizes - self.assertEqual(eco.nplayers, 4) + self.assertEqual(eco.num_players, 4) self.assertEqual(len(pops), 1) self.assertEqual(len(pops[0]), 4) self.assertAlmostEqual(sum(pops[0]), 1.0) @@ -48,7 +48,7 @@ def test_init(self): # Distribution will automatically normalise eco = axelrod.Ecosystem(self.res_cooperators, population=[70, 25, 3, 2]) pops = eco.population_sizes - self.assertEqual(eco.nplayers, 4) + self.assertEqual(eco.num_players, 4) self.assertEqual(len(pops), 1) self.assertEqual(len(pops[0]), 4) self.assertAlmostEqual(sum(pops[0]), 1.0) diff --git a/axelrod/tests/unit/test_fingerprint.py b/axelrod/tests/unit/test_fingerprint.py index f925ad477..7bd31faf7 100644 --- a/axelrod/tests/unit/test_fingerprint.py +++ b/axelrod/tests/unit/test_fingerprint.py @@ -187,19 +187,11 @@ def test_temp_file_creation(self): af = AshlockFingerprint(self.strategy, self.probe) filename = "test_outputs/test_fingerprint.csv" - # No temp file is created. - af.fingerprint(turns=1, repetitions=1, step=0.5, progress_bar=False, - in_memory=True) - af.fingerprint(turns=1, repetitions=1, step=0.5, progress_bar=False, - in_memory=True, filename=filename) - af.fingerprint(turns=1, repetitions=1, step=0.5, progress_bar=False, - in_memory=False, filename=filename) - self.assertEqual(RecordedMksTemp.record, []) # Temp file is created and destroyed. af.fingerprint(turns=1, repetitions=1, step=0.5, progress_bar=False, - in_memory=False, filename=None) + filename=None) self.assertEqual(len(RecordedMksTemp.record), 1) filename = RecordedMksTemp.record[0][1] @@ -214,19 +206,7 @@ def test_fingerprint_with_filename(self): filename=filename) with open(filename, 'r') as out: data = out.read() - self.assertEqual(len(data.split("\n")), 10) - - def test_in_memory_fingerprint(self): - af = AshlockFingerprint(self.strategy, self.probe) - af.fingerprint(turns=10, repetitions=2, step=0.5, progress_bar=False, - in_memory=True) - edge_keys = sorted(list(af.interactions.keys())) - coord_keys = sorted(list(af.data.keys())) - self.assertEqual(af.step, 0.5) - self.assertEqual(af.spatial_tournament.interactions_dict, - af.interactions) - self.assertEqual(edge_keys, self.expected_edges) - self.assertEqual(coord_keys, self.expected_points) + self.assertEqual(len(data.split("\n")), 20) def test_serial_fingerprint(self): af = AshlockFingerprint(self.strategy, self.probe) @@ -431,7 +411,7 @@ def test_fingerprint_with_filename(self): filename=filename) with open(filename, 'r') as out: data = out.read() - self.assertEqual(len(data.split("\n")), 50 + 1) + self.assertEqual(len(data.split("\n")), 102) def test_serial_fingerprint(self): strategy = axl.TitForTat() @@ -452,14 +432,23 @@ def test_analyse_cooperation_ratio(self): filename = "test_outputs/test_fingerprint.csv" with open(filename, "w") as f: f.write( -"""0,1,Player0,Player1,CCC,DDD -0,1,Player0,Player1,CCC,DDD -0,2,Player0,Player2,CCD,DDD -0,2,Player0,Player2,CCC,DDD -0,3,Player0,Player3,CCD,DDD -0,3,Player0,Player3,DCC,DDD -0,4,Player0,Player3,DDD,DDD -0,4,Player0,Player3,DDD,DDD""") +"""Interaction index,Player index,Opponent index,Repetition,Player name,Opponent name,Actions +0,0,1,0,Player0,Player1,CCC +0,1,0,0,Player1,Player0,DDD +1,0,1,1,Player0,Player1,CCC +1,1,0,1,Player1,Player0,DDD +2,0,2,0,Player0,Player2,CCD +2,2,0,0,Player2,Player0,DDD +3,0,2,1,Player0,Player2,CCC +3,2,0,1,Player2,Player0,DDD +4,0,3,0,Player0,Player3,CCD +4,3,0,0,Player3,Player0,DDD +5,0,3,1,Player0,Player3,DCC +5,3,0,1,Player3,Player0,DDD +6,0,4,2,Player0,Player4,DDD +6,4,0,2,Player4,Player0,DDD +7,0,4,3,Player0,Player4,DDD +7,4,0,3,Player4,Player0,DDD""") data = tf.analyse_cooperation_ratio(filename) expected_data = np.array([[1, 1, 1], [1, 1, 1 / 2], diff --git a/axelrod/tests/unit/test_plot.py b/axelrod/tests/unit/test_plot.py index 4a31fe95f..767393fe1 100644 --- a/axelrod/tests/unit/test_plot.py +++ b/axelrod/tests/unit/test_plot.py @@ -12,33 +12,21 @@ class TestPlot(unittest.TestCase): @classmethod def setUpClass(cls): - cls.players = ( - axelrod.Alternator(), axelrod.TitForTat(), axelrod.Defector()) + cls.filename = "test_outputs/test_results.csv" + + cls.players = [axelrod.Alternator(), axelrod.TitForTat(), + axelrod.Defector()] + cls.repetitions = 3 cls.turns = 5 - cls.matches = { - (0, 1): [axelrod.Match( - (cls.players[0], cls.players[1]), turns=cls.turns) - for _ in range(3)], - (0, 2): [axelrod.Match( - (cls.players[0], cls.players[2]), turns=cls.turns) - for _ in range(3)], - (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) - - cls.interactions = {} - for index_pair, matches in cls.matches.items(): - for match in matches: - match.play() - try: - cls.interactions[index_pair].append(match.result) - except KeyError: - cls.interactions[index_pair] = [match.result] - - cls.test_result_set = axelrod.ResultSet(cls.players, cls.interactions, + + cls.test_result_set = axelrod.ResultSet(cls.filename, + cls.players, + cls.repetitions, + progress_bar=False) + + cls.test_result_set = axelrod.ResultSet(cls.filename, + cls.players, + cls.repetitions, progress_bar=False) cls.expected_boxplot_dataset = [ [(17 / 5 + 9 / 5) / 2 for _ in range(3)], @@ -110,15 +98,16 @@ def test_init(self): def test_init_from_resulsetfromfile(self): tmp_file = tempfile.NamedTemporaryFile(mode='w', delete=False) + players=[axelrod.Cooperator(), + axelrod.TitForTat(), + axelrod.Defector()] tournament = axelrod.Tournament( - players=[axelrod.Cooperator(), - axelrod.TitForTat(), - axelrod.Defector()], + players=players, turns=2, repetitions=2) tournament.play(filename=tmp_file.name, progress_bar=False) tmp_file.close() - rs = axelrod.ResultSetFromFile(tmp_file.name, progress_bar=False) + rs = axelrod.ResultSet(tmp_file.name, players, 2, progress_bar=False) plot = axelrod.Plot(rs) self.assertEqual(plot.result_set, rs) diff --git a/axelrod/tests/unit/test_resultset.py b/axelrod/tests/unit/test_resultset.py index a5a93adfb..9fa8fec73 100644 --- a/axelrod/tests/unit/test_resultset.py +++ b/axelrod/tests/unit/test_resultset.py @@ -4,9 +4,12 @@ from hypothesis import given, settings from numpy import mean, std, nanmedian +from dask.dataframe.core import DataFrame +import pandas as pd import axelrod import axelrod.interaction_utils as iu +from axelrod.result_set import create_counter_dict from axelrod.tests.property import tournaments, prob_end_tournaments @@ -22,30 +25,9 @@ def setUpClass(cls): cls.players = [axelrod.Alternator(), axelrod.TitForTat(), axelrod.Defector()] + cls.repetitions = 3 cls.turns = 5 - cls.matches = { - (0, 1): [axelrod.Match((cls.players[0], cls.players[1]), - turns=cls.turns) for _ in range(3)], - (0, 2): [axelrod.Match((cls.players[0], cls.players[2]), - turns=cls.turns) for _ in range(3)], - (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) - - cls.interactions = {} - for index_pair, matches in cls.matches.items(): - for match in matches: - match.play() - try: - cls.interactions[index_pair].append(match.result) - except KeyError: - cls.interactions[index_pair] = [match.result] - - cls.expected_players_to_match_dicts = { - 0: cls.matches[(0, 1)] + cls.matches[(0, 2)], - 1: cls.matches[(0, 1)] + cls.matches[(1, 2)], - 2: cls.matches[(1, 2)] + cls.matches[(0, 2)]} + cls.edges = [(0, 1), (0, 2), (1, 2)] cls.expected_match_lengths = [ [[0, 5, 5], [5, 0, 5], [5, 5, 0]] @@ -128,7 +110,7 @@ def setUpClass(cls): cls.expected_normalised_cooperation = [ [0, mean([3 / 5 for _ in range(3)]), - mean([3 / 5 for _ in range(3)])], + mean([3 / 5 for _ in range(3)])], [mean([3 / 5 for _ in range(3)]), 0, mean([1 / 5 for _ in range(3)])], [0, 0, 0], @@ -219,52 +201,31 @@ def setUpClass(cls): ] def test_init(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertEqual(rs.players, self.players) - self.assertEqual(rs.nplayers, len(self.players)) - self.assertEqual(rs.interactions, self.interactions) - for inter in self.interactions.values(): - self.assertEqual(rs.repetitions, len(inter)) - - # Test structure of matches - # This is really a test of the test - for index_pair, repetitions in rs.interactions.items(): - self.assertIsInstance(repetitions, list) - self.assertIsInstance(index_pair, tuple) - for interaction in repetitions: - self.assertIsInstance(interaction, list) - self.assertEqual(len(interaction), self.turns) - - def test_init_with_repetitions(self): - rs = axelrod.ResultSet(self.players, self.interactions, - repetitions=3, - progress_bar=False) + self.assertEqual(rs.num_players, len(self.players)) + + def test_init_multiprocessing(self): + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, + progress_bar=False, processes=2) self.assertEqual(rs.players, self.players) - self.assertEqual(rs.nplayers, len(self.players)) - self.assertEqual(rs.interactions, self.interactions) - self.assertEqual(rs.repetitions, 3) + self.assertEqual(rs.num_players, len(self.players)) - def test_init_with_different_game(self): - game = axelrod.Game(p=-1, r=-1, s=-1, t=-1) - rs = axelrod.ResultSet(self.players, self.interactions, - progress_bar=False, game=game) - self.assertEqual(rs.game.RPST(), (-1, -1, -1, -1)) + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, + progress_bar=False, processes=0) + self.assertEqual(rs.players, self.players) + self.assertEqual(rs.num_players, len(self.players)) def test_with_progress_bar(self): - rs = axelrod.ResultSet(self.players, self.interactions) - self.assertTrue(rs.progress_bar) - self.assertEqual(rs.progress_bar.total, 13 + 2 * rs.nplayers) - self.assertEqual(rs.progress_bar.n, rs.progress_bar.total) - - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=True) self.assertTrue(rs.progress_bar) - self.assertEqual(rs.progress_bar.total, 13 + 2 * rs.nplayers) + self.assertEqual(rs.progress_bar.total, 25) self.assertEqual(rs.progress_bar.n, rs.progress_bar.total) def test_match_lengths(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.match_lengths, list) self.assertEqual(len(rs.match_lengths), rs.repetitions) @@ -284,68 +245,62 @@ def test_match_lengths(self): else: self.assertEqual(length, self.turns) + def test_scores(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.scores, list) - self.assertEqual(len(rs.scores), rs.nplayers) + self.assertEqual(len(rs.scores), rs.num_players) self.assertEqual(rs.scores, self.expected_scores) - def test_scores_with_different_game(self): - game = axelrod.Game(p=-1, r=-1, s=-1, t=-1) - rs = axelrod.ResultSet(self.players, self.interactions, - progress_bar=False, game=game) - for player in rs.scores: - for score in player: - self.assertFalse(score > 0) def test_ranking(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.ranking, list) - self.assertEqual(len(rs.ranking), rs.nplayers) + self.assertEqual(len(rs.ranking), rs.num_players) self.assertEqual(rs.ranking, self.expected_ranking) def test_ranked_names(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.ranked_names, list) - self.assertEqual(len(rs.ranked_names), rs.nplayers) + self.assertEqual(len(rs.ranked_names), rs.num_players) self.assertEqual(rs.ranked_names, self.expected_ranked_names) def test_wins(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.wins, list) - self.assertEqual(len(rs.wins), rs.nplayers) + self.assertEqual(len(rs.wins), rs.num_players) self.assertEqual(rs.wins, self.expected_wins) def test_normalised_scores(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.normalised_scores, list) - self.assertEqual(len(rs.normalised_scores), rs.nplayers) + self.assertEqual(len(rs.normalised_scores), rs.num_players) self.assertEqual(rs.normalised_scores, self.expected_normalised_scores) def test_payoffs(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.payoffs, list) - self.assertEqual(len(rs.payoffs), rs.nplayers) + self.assertEqual(len(rs.payoffs), rs.num_players) self.assertEqual(rs.payoffs, self.expected_payoffs) def test_payoff_matrix(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.payoff_matrix, list) - self.assertEqual(len(rs.payoff_matrix), rs.nplayers) + self.assertEqual(len(rs.payoff_matrix), rs.num_players) self.assertEqual(rs.payoff_matrix, self.expected_payoff_matrix) def test_score_diffs(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.score_diffs, list) - self.assertEqual(len(rs.score_diffs), rs.nplayers) + self.assertEqual(len(rs.score_diffs), rs.num_players) for i, row in enumerate(rs.score_diffs): for j, col in enumerate(row): for k, score in enumerate(col): @@ -353,144 +308,152 @@ def test_score_diffs(self): self.expected_score_diffs[i][j][k]) def test_payoff_diffs_means(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.payoff_diffs_means, list) - self.assertEqual(len(rs.payoff_diffs_means), rs.nplayers) + self.assertEqual(len(rs.payoff_diffs_means), rs.num_players) 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.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.payoff_stddevs, list) - self.assertEqual(len(rs.payoff_stddevs), rs.nplayers) + self.assertEqual(len(rs.payoff_stddevs), rs.num_players) self.assertEqual(rs.payoff_stddevs, self.expected_payoff_stddevs) def test_cooperation(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.cooperation, list) - self.assertEqual(len(rs.cooperation), rs.nplayers) + self.assertEqual(len(rs.cooperation), rs.num_players) self.assertEqual(rs.cooperation, self.expected_cooperation) def test_initial_cooperation_count(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.initial_cooperation_count, list) - self.assertEqual(len(rs.initial_cooperation_count), rs.nplayers) + self.assertEqual(len(rs.initial_cooperation_count), rs.num_players) self.assertEqual(rs.initial_cooperation_count, self.expected_initial_cooperation_count) def test_normalised_cooperation(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.normalised_cooperation, list) - self.assertEqual(len(rs.normalised_cooperation), rs.nplayers) - self.assertEqual(rs.normalised_cooperation, - self.expected_normalised_cooperation) + self.assertEqual(len(rs.normalised_cooperation), rs.num_players) + for i, row in enumerate(rs.normalised_cooperation): + for j, col in enumerate(row): + self.assertAlmostEqual(col, + self.expected_normalised_cooperation[i][j]) def test_initial_cooperation_rate(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.initial_cooperation_rate, list) - self.assertEqual(len(rs.initial_cooperation_rate), rs.nplayers) + self.assertEqual(len(rs.initial_cooperation_rate), rs.num_players) self.assertEqual(rs.initial_cooperation_rate, self.expected_initial_cooperation_rate) def test_state_distribution(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.state_distribution, list) - self.assertEqual(len(rs.state_distribution), rs.nplayers) + self.assertEqual(len(rs.state_distribution), rs.num_players) self.assertEqual(rs.state_distribution, self.expected_state_distribution) def test_state_normalised_distribution(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.normalised_state_distribution, list) - self.assertEqual(len(rs.normalised_state_distribution), rs.nplayers) + self.assertEqual(len(rs.normalised_state_distribution), rs.num_players) self.assertEqual(rs.normalised_state_distribution, self.expected_normalised_state_distribution) def test_state_to_action_distribution(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.state_to_action_distribution, list) - self.assertEqual(len(rs.state_to_action_distribution), rs.nplayers) - self.assertEqual(rs.state_to_action_distribution, - self.expected_state_to_action_distribution) + self.assertEqual(len(rs.state_to_action_distribution), rs.num_players) + self.assertEqual(rs.state_to_action_distribution[1], + self.expected_state_to_action_distribution[1]) def test_normalised_state_to_action_distribution(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.normalised_state_to_action_distribution, list) self.assertEqual(len(rs.normalised_state_to_action_distribution), - rs.nplayers) + rs.num_players) self.assertEqual(rs.normalised_state_to_action_distribution, self.expected_normalised_state_to_action_distribution) def test_vengeful_cooperation(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.vengeful_cooperation, list) - self.assertEqual(len(rs.vengeful_cooperation), rs.nplayers) - self.assertEqual(rs.vengeful_cooperation, - self.expected_vengeful_cooperation) + self.assertEqual(len(rs.vengeful_cooperation), rs.num_players) + for i, row in enumerate(rs.vengeful_cooperation): + for j, col in enumerate(row): + self.assertAlmostEqual(col, + self.expected_vengeful_cooperation[i][j]) def test_cooperating_rating(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.cooperating_rating, list) - self.assertEqual(len(rs.cooperating_rating), rs.nplayers) + self.assertEqual(len(rs.cooperating_rating), rs.num_players) self.assertEqual(rs.cooperating_rating, self.expected_cooperating_rating) def test_good_partner_matrix(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.good_partner_matrix, list) - self.assertEqual(len(rs.good_partner_matrix), rs.nplayers) + self.assertEqual(len(rs.good_partner_matrix), rs.num_players) self.assertEqual(rs.good_partner_matrix, self.expected_good_partner_matrix) def test_good_partner_rating(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.good_partner_rating, list) - self.assertEqual(len(rs.good_partner_rating), rs.nplayers) + self.assertEqual(len(rs.good_partner_rating), rs.num_players) self.assertEqual(rs.good_partner_rating, self.expected_good_partner_rating) def test_eigenjesus_rating(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.eigenjesus_rating, list) - self.assertEqual(len(rs.eigenjesus_rating), rs.nplayers) + self.assertEqual(len(rs.eigenjesus_rating), rs.num_players) 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.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.eigenmoses_rating, list) - self.assertEqual(len(rs.eigenmoses_rating), rs.nplayers) + self.assertEqual(len(rs.eigenmoses_rating), rs.num_players) for j, rate in enumerate(rs.eigenmoses_rating): self.assertAlmostEqual(rate, self.expected_eigenmoses_rating[j]) def test_self_interaction_for_random_strategies(self): # Based on https://github.com/Axelrod-Python/Axelrod/issues/670 + # Note that the conclusion of #670 is incorrect and only includes one of + # the copies of the strategy. axelrod.seed(0) players = [s() for s in axelrod.demo_strategies] tournament = axelrod.Tournament(players, repetitions=2, turns=5) results = tournament.play(progress_bar=False) - self.assertEqual(results.payoff_diffs_means[-1][-1], 1.0) + self.assertEqual(results.payoff_diffs_means[-1][-1], 0.0) def test_equality(self): - rs_sets = [axelrod.ResultSet(self.players, self.interactions, + rs_sets = [axelrod.ResultSet(self.filename, + self.players, + self.repetitions, progress_bar=False) for _ in range(2)] self.assertEqual(rs_sets[0], rs_sets[1]) @@ -500,7 +463,7 @@ def test_equality(self): self.assertNotEqual(results, rs_sets[0]) def test_summarise(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) sd = rs.summarise() @@ -566,10 +529,10 @@ def test_summarise_regression_test(self): places=3) def test_write_summary(self): - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) - rs.write_summary(filename=self.filename) - with open(self.filename, "r") as csvfile: + rs.write_summary(filename=self.filename + ".summary") + with open(self.filename + ".summary", "r") as csvfile: ranked_names = [] csvreader = csv.reader(csvfile) for row in csvreader: @@ -579,222 +542,6 @@ def test_write_summary(self): self.assertEqual(ranked_names[1:], rs.ranked_names) -class TestResultSetFromFile(unittest.TestCase): - filename = "test_outputs/test_results_from_file.csv" - players = [axelrod.Cooperator(), - axelrod.TitForTat(), - axelrod.Defector()] - tournament = axelrod.Tournament(players=players, turns=2, repetitions=3) - tournament.play(filename=filename, progress_bar=False) - - interactions = iu.read_interactions_from_file(filename, progress_bar=False) - - def test_init(self): - brs = axelrod.ResultSetFromFile(self.filename, progress_bar=False) - self.assertEqual(brs.players, [str(p) for p in self.players]) - self.assertEqual(brs.nplayers, len(self.players)) - self.assertEqual(brs.repetitions, 3) - - def test_init_with_different_game(self): - game = axelrod.Game(p=-1, r=-1, s=-1, t=-1) - brs = axelrod.ResultSetFromFile(self.filename, progress_bar=False, - game=game) - self.assertEqual(brs.game.RPST(), (-1, -1, -1, -1)) - - def test_init_with_progress_bar(self): - """Just able to test that no error occurs""" - brs = axelrod.ResultSetFromFile(self.filename, progress_bar=True) - self.assertEqual(brs.nplayers, len(self.players)) - self.assertEqual(brs.repetitions, 3) - self.assertEqual(brs.num_interactions, 18) - - def test_init_with_num_interactions(self): - """Just able to test that no error occurs""" - brs = axelrod.ResultSetFromFile(self.filename, progress_bar=False, - num_interactions=18) - self.assertEqual(brs.nplayers, len(self.players)) - self.assertEqual(brs.repetitions, 3) - self.assertEqual(brs.num_interactions, 18) - - def test_init_with_players_repetitions(self): - """Just able to test that no error occurs""" - brs = axelrod.ResultSetFromFile(self.filename, progress_bar=False, - num_interactions=18, repetitions=3, - players=[str(p) for p in self.players]) - self.assertEqual(brs.nplayers, len(self.players)) - self.assertEqual(brs.repetitions, 3) - self.assertEqual(brs.num_interactions, 18) - - def test_equality(self): - """A test that checks overall equality by comparing to the base result - set class""" - brs = axelrod.ResultSetFromFile(self.filename, progress_bar=False) - rs = axelrod.ResultSet(self.players, self.interactions, progress_bar=False) - self.assertEqual(rs, brs) - - def test_interactions_equality(self): - brs = axelrod.ResultSetFromFile(self.filename, progress_bar=False, - keep_interactions=True) - rs = axelrod.ResultSet(self.players, self.interactions, progress_bar=False) - self.assertEqual(rs.interactions, brs.interactions) - - @given(tournament=tournaments(max_size=5, - max_turns=5, - max_noise=0, - max_repetitions=3)) - @settings(max_examples=5, max_iterations=20) - def test_equality_with_round_robin(self, tournament): - filename = "test_outputs/test_results.csv" - tournament.play(filename=filename, progress_bar=False, - build_results=False) - brs = axelrod.ResultSetFromFile(filename, progress_bar=False) - interactions = iu.read_interactions_from_file(filename, - progress_bar=False) - rs = axelrod.ResultSet(tournament.players, interactions, - progress_bar=False) - - # Not testing full equality because of floating point errors. - self.assertEqual(rs.scores, brs.scores) - self.assertEqual(rs.wins, brs.wins) - self.assertEqual(rs.match_lengths, brs.match_lengths) - self.assertEqual(rs.cooperation, brs.cooperation) - - # Test that players are in the results (due to floating point errors - # the order might not be the same) - self.assertEqual(set(rs.ranked_names), set(brs.ranked_names)) - - @given(tournament=prob_end_tournaments(max_size=5, - min_prob_end=.7, - max_repetitions=3)) - @settings(max_examples=5, max_iterations=20) - def test_equality_with_prob_end(self, tournament): - filename = "test_outputs/test_results.csv" - tournament.play(filename=filename, progress_bar=False, - build_results=False) - brs = axelrod.ResultSetFromFile(filename, progress_bar=False) - interactions = iu.read_interactions_from_file(filename, - progress_bar=False) - rs = axelrod.ResultSet(tournament.players, interactions, - progress_bar=False) - - # Not testing full equality because of floating point errors. - self.assertEqual(rs.ranked_names, brs.ranked_names) - self.assertEqual(rs.scores, brs.scores) - self.assertEqual(rs.match_lengths, brs.match_lengths) - self.assertEqual(rs.cooperation, brs.cooperation) - - def test_read_players_and_repetitions(self): - brs = axelrod.ResultSetFromFile(self.filename, progress_bar=False) - players, repetitions = brs._read_players_and_repetition_numbers() - expected_players = ['Cooperator', 'Tit For Tat', 'Defector'] - self.assertEqual(brs.players, expected_players) - self.assertEqual(repetitions, 3) - - def test_update_repetitions(self): - brs = axelrod.ResultSetFromFile(filename=self.filename, progress_bar=False) - brs.repetitions_d = {} - brs._update_repetitions((0, 0)) - self.assertEqual(brs.repetitions_d, {(0, 0): 1}) - brs._update_repetitions((0, 0)) - self.assertEqual(brs.repetitions_d, {(0, 0): 2}) - brs._update_repetitions((0, 1)) - self.assertEqual(brs.repetitions_d, {(0, 0): 2, (0, 1): 1}) - - def test_build_repetitions(self): - brs = axelrod.ResultSetFromFile(self.filename, progress_bar=False) - brs.repetitions_d = {} - brs._update_repetitions((0, 0)) - brs._update_repetitions((0, 0)) - repetitions = brs._build_repetitions() - self.assertEqual(repetitions, 2) - self.assertFalse(hasattr(brs, 'repetitions_d')) - - def test_update_players(self): - brs = axelrod.ResultSetFromFile(self.filename, progress_bar=False) - brs.players_d = {} - brs._update_players((0, 0), ('Cooperator', 'Cooperator')) - self.assertEqual(brs.players_d, {0: 'Cooperator'}) - brs._update_players((0, 0), ('Cooperator', 'Cooperator')) - self.assertEqual(brs.players_d, {0: 'Cooperator'}) - brs._update_players((0, 1), ('Cooperator', 'Defector')) - self.assertEqual(brs.players_d, {0: 'Cooperator', 1: 'Defector'}) - - def test_build_players(self): - brs = axelrod.ResultSetFromFile(self.filename, progress_bar=False) - brs.players_d = {} - brs._update_players((0, 0), ('Cooperator', 'Cooperator')) - brs._update_players((0, 1), ('Cooperator', 'Defector')) - players = brs._build_players() - self.assertEqual(players, ['Cooperator', 'Defector']) - self.assertFalse(hasattr(brs, 'players_d')) - - def test_build_read_match_chunks(self): - brs = axelrod.ResultSetFromFile(self.filename, progress_bar=False) - matches = brs.read_match_chunks() - chunk = next(matches) - self.assertEqual(chunk[0], - ['0'] * 2 + ['Cooperator'] * 2 + [(C, C)] * 2) - self.assertEqual(chunk[1], - ['0'] * 2 + ['Cooperator'] * 2 + [(C, C)] * 2) - self.assertEqual(chunk[2], - ['0'] * 2 + ['Cooperator'] * 2 + [(C, C)] * 2) - self.assertEqual(len(list(matches)), 5) - - def test_build_all(self): - brs = axelrod.ResultSetFromFile(self.filename, progress_bar=False) - rs = axelrod.ResultSet(self.players, self.interactions, - progress_bar=False) - - brs._build_empty_metrics() - self.assertNotEqual(brs, rs) - brs._build_score_related_metrics(progress_bar=False) - self.assertEqual(brs, rs) - - def test_buid_empty_metrics(self): - plist = range(3) - repetitions = 3 - replist = range(repetitions) - expected_match_lengths = [[[0 for opponent in plist] for player in plist] - for _ in replist] - expected_wins = [[0 for _ in replist] for player in plist] - expected_scores = [[0 for _ in replist] for player in plist] - expected_normalised_scores = [[[] for _ in replist] for player in plist] - expected_payoffs = [[[] for opponent in plist] for player in plist] - expected_score_diffs = [[[0] * repetitions for opponent in plist] - for player in plist] - expected_cooperation = [[0 for opponent in plist] for player in plist] - expected_normalised_cooperation = [[[] for opponent in plist] - for player in plist] - expected_good_partner_matrix = [[0 for opponent in plist] - for player in plist] - - expected_good_partner_rating = [0 for player in plist] - brs = axelrod.ResultSetFromFile(self.filename, progress_bar=False) - brs.match_lengths = [] - brs.wins = [] - brs.scores = [] - brs.normalised_scores = [] - brs.payoffs = [] - brs.score_diffs = [] - brs.cooperation = [] - brs.normalised_cooperation = [] - brs.good_partner_matrix = [] - brs.total_interactions = [] - brs.good_partner_rating = [] - brs._build_empty_metrics() - self.assertEqual(brs.match_lengths, expected_match_lengths) - self.assertEqual(brs.wins, expected_wins) - self.assertEqual(brs.scores, expected_scores) - self.assertEqual(brs.normalised_scores, expected_normalised_scores) - self.assertEqual(brs.payoffs, expected_payoffs) - self.assertEqual(brs.score_diffs, expected_score_diffs) - self.assertEqual(brs.cooperation, expected_cooperation) - self.assertEqual(brs.normalised_cooperation, - expected_normalised_cooperation) - self.assertEqual(brs.good_partner_matrix, expected_good_partner_matrix) - self.assertEqual(brs.good_partner_rating, expected_good_partner_rating) - - class TestDecorator(unittest.TestCase): def test_update_progress_bar(self): method = lambda x: None @@ -809,29 +556,11 @@ class TestResultSetSpatialStructure(TestResultSet): @classmethod def setUpClass(cls): + cls.filename = "test_outputs/test_results_spatial.csv" cls.players = [axelrod.Alternator(), axelrod.TitForTat(), axelrod.Defector()] cls.turns = 5 cls.edges = [(0, 1), (0, 2)] - cls.matches = { - (0, 1): [axelrod.Match((cls.players[0], cls.players[1]), - turns=cls.turns) for _ in range(3)], - (0, 2): [axelrod.Match((cls.players[0], cls.players[2]), - turns=cls.turns) for _ in range(3)]} - - cls.interactions = {} - for index_pair, matches in cls.matches.items(): - for match in matches: - match.play() - try: - cls.interactions[index_pair].append(match.result) - except KeyError: - cls.interactions[index_pair] = [match.result] - - cls.expected_players_to_match_dicts = { - 0: cls.matches[(0, 1)] + cls.matches[(0, 2)], - 1: cls.matches[(0, 1)], - 2: cls.matches[(0, 2)]} cls.expected_match_lengths = [ [[0, 5, 5], [5, 0, 0], [5, 0, 0]] @@ -1012,7 +741,7 @@ def test_match_lengths(self): of players-nodes that are end vertices of an edge is equal to the number of turns. Otherwise it is 0. """ - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) self.assertIsInstance(rs.match_lengths, list) self.assertEqual(len(rs.match_lengths), rs.repetitions) @@ -1040,28 +769,11 @@ class TestResultSetSpatialStructureTwo(TestResultSetSpatialStructure): @classmethod def setUpClass(cls): + cls.filename = "test_outputs/test_results_spatial_two.csv" cls.players = [axelrod.Alternator(), axelrod.TitForTat(), axelrod.Defector(), axelrod.Cooperator()] cls.turns = 5 cls.edges = [(0, 1), (2, 3)] - cls.matches = {(0, 1): [axelrod.Match((cls.players[0], cls.players[1]), - turns=cls.turns) for _ in range(3)], - (2, 3): [axelrod.Match((cls.players[2], cls.players[3]), - turns=cls.turns) for _ in range(3)]} - - cls.interactions = {} - for index_pair, matches in cls.matches.items(): - for match in matches: - match.play() - try: - cls.interactions[index_pair].append(match.result) - except KeyError: - cls.interactions[index_pair] = [match.result] - - cls.expected_players_to_match_dicts = {0: cls.matches[(0, 1)], - 1: cls.matches[(0, 1)], - 2: cls.matches[(2, 3)], - 3: cls.matches[(2, 3)]} cls.expected_match_lengths = [ [[0, 5, 0, 0], [5, 0, 0, 0], [0, 0, 0, 5], [0, 0, 5, 0]] @@ -1260,28 +972,11 @@ class TestResultSetSpatialStructureThree(TestResultSetSpatialStructure): @classmethod def setUpClass(cls): + cls.filename = "test_outputs/test_results_spatial_three.csv" cls.players = [axelrod.Alternator(), axelrod.TitForTat(), axelrod.Defector(), axelrod.Cooperator()] cls.turns = 5 cls.edges = [(0, 0), (1, 1), (2, 2), (3, 3)] - cls.matches = {(i, i): [axelrod.Match( - (cls.players[i], cls.players[i].clone()), turns=cls.turns) - for _ in range(3)] for i in range(4)} - - cls.interactions = {} - for index_pair, matches in cls.matches.items(): - for match in matches: - match.play() - - try: - cls.interactions[index_pair].append(match.result) - except KeyError: - cls.interactions[index_pair] = [match.result] - - cls.expected_players_to_match_dicts = {0: cls.matches[(0, 0)], - 1: cls.matches[(1, 1)], - 2: cls.matches[(2, 2)], - 3: cls.matches[(3, 3)]} cls.expected_match_lengths =[ [[5, 0, 0, 0], [0, 5, 0, 0], [0, 0, 5, 0], [0, 0, 0, 5]] @@ -1342,7 +1037,10 @@ def setUpClass(cls): ] cls.expected_cooperation = [ - [0.0 for _ in range(4)] for _ in range(4) + [9.0, 0, 0, 0], + [0, 15.0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 15.0] ] cls.expected_normalised_cooperation = [ @@ -1419,7 +1117,7 @@ def test_equality(self): def test_summarise(self): """Overwriting for this particular case""" - rs = axelrod.ResultSet(self.players, self.interactions, + rs = axelrod.ResultSet(self.filename, self.players, self.repetitions, progress_bar=False) sd = rs.summarise() @@ -1432,7 +1130,8 @@ def test_summarise(self): class TestSummary(unittest.TestCase): """Separate test to check that summary always builds without failures""" - @given(tournament=tournaments(max_size=5, + @given(tournament=tournaments(min_size=2, + max_size=5, max_turns=5, max_repetitions=3)) @settings(max_examples=5, max_iterations=20) @@ -1447,3 +1146,15 @@ def test_summarise_without_failure(self, tournament): player.DC_rate + player.DD_rate, 3) self.assertTrue(total_rate in [0, 1]) self.assertTrue(0 <= player.Initial_C_rate <= 1) + + +class TestCreateCounterDict(unittest.TestCase): + """Separate test for a helper function""" + def test_basic_use(self): + key_map = {"Col 1": "Var 1", "Col 2": "Var 2"} + df = pd.DataFrame({"Col 1": [10, 20, 30], "Col 2": [1, 2, 0]}, + index=[[5, 6, 7], [1, 2, 3]]) + self.assertEqual(create_counter_dict(df, 6, 2, key_map), + Counter({"Var 1": 20, "Var 2": 2})) + self.assertEqual(create_counter_dict(df, 7, 3, key_map), + Counter({"Var 1": 30})) diff --git a/axelrod/tests/unit/test_tournament.py b/axelrod/tests/unit/test_tournament.py index ca57906ab..af1dea525 100644 --- a/axelrod/tests/unit/test_tournament.py +++ b/axelrod/tests/unit/test_tournament.py @@ -9,10 +9,12 @@ import unittest from unittest.mock import MagicMock, patch import warnings +import filecmp from hypothesis import given, example, settings from hypothesis.strategies import integers, floats from tqdm import tqdm +import numpy as np from axelrod.tests.property import (tournaments, prob_end_tournaments, @@ -140,26 +142,15 @@ def test_warning(self): filename=self.filename, progress_bar=False) self.assertEqual(len(w), 0) - def test_setup_output_in_memory_overrides_filename(self): - self.assertIsNone(self.test_tournament.filename) - self.assertIsNone(self.test_tournament._temp_file_descriptor) - self.assertFalse(hasattr(self.test_tournament, 'interactions_dict')) - - self.test_tournament.setup_output(self.filename, in_memory=True) - - self.assertIsNone(self.test_tournament.filename) - self.assertIsNone(self.test_tournament._temp_file_descriptor) - self.assertEqual(self.test_tournament.interactions_dict, {}) - def test_setup_output_with_filename(self): - self.test_tournament.setup_output(self.filename, in_memory=False) + self.test_tournament.setup_output(self.filename) self.assertEqual(self.test_tournament.filename, self.filename) self.assertIsNone(self.test_tournament._temp_file_descriptor) self.assertFalse(hasattr(self.test_tournament, 'interactions_dict')) - def test_setup_output_no_filename_no_in_memory(self): + def test_setup_output_no_filename(self): self.test_tournament.setup_output() self.assertIsInstance(self.test_tournament.filename, str) @@ -190,22 +181,17 @@ def test_play_changes_temp_file_descriptor(self): self.assertIsNone(self.test_tournament._temp_file_descriptor) # No file descriptor for a named file. - self.test_tournament.play(filename=self.filename, in_memory=False, - progress_bar=False) - self.assertIsNone(self.test_tournament._temp_file_descriptor) - - # No file descriptor for in_memory. - self.test_tournament.play(filename=None, in_memory=True, + self.test_tournament.play(filename=self.filename, progress_bar=False) self.assertIsNone(self.test_tournament._temp_file_descriptor) # Temp file creates file descriptor. - self.test_tournament.play(filename=None, in_memory=False, + self.test_tournament.play(filename=None, progress_bar=False) self.assertIsInstance(self.test_tournament._temp_file_descriptor, int) def test_play_tempfile_removed(self): - self.test_tournament.play(filename=None, in_memory=False, + self.test_tournament.play(filename=None, progress_bar=False) self.assertFalse(os.path.isfile(self.test_tournament.filename)) @@ -227,10 +213,6 @@ def test_play_resets_filename_and_temp_file_descriptor_each_time(self): self.assertNotEqual(old_filename, self.test_tournament.filename) self.assertNotEqual(self.test_tournament.filename, self.filename) - self.test_tournament.play(in_memory=True, progress_bar=False) - self.assertIsNone(self.test_tournament._temp_file_descriptor) - self.assertIsNone(self.test_tournament.filename) - def test_get_file_objects_no_filename(self): file, writer = self.test_tournament._get_file_objects() self.assertIsNone(file) @@ -295,7 +277,7 @@ def test_serial_play_with_different_game(self): turns=1, repetitions=1) results = tournament.play(progress_bar=False) - self.assertEqual(results.game.RPST(), (-1, -1, -1, -1)) + self.assertLessEqual(np.max(results.scores), 0) @patch('tqdm.tqdm', RecordedTQDM) def test_no_progress_bar_play(self): @@ -323,9 +305,6 @@ def test_no_progress_bar_play(self): self.assertIsNone(results) self.assertEqual(RecordedTQDM.record, []) - results = axelrod.ResultSetFromFile(self.filename, progress_bar=False) - self.assertIsInstance(results, axelrod.ResultSet) - def assert_play_pbar_correct_total_and_finished(self, pbar, total): self.assertEqual(pbar.desc, 'Playing matches') self.assertEqual(pbar.total, total) @@ -346,7 +325,7 @@ def test_progress_bar_play(self): results = tournament.play() self.assertIsInstance(results, axelrod.ResultSet) # Check that progress bar was created, updated and closed. - self.assertEqual(len(RecordedTQDM.record), 3) + self.assertEqual(len(RecordedTQDM.record), 2) play_pbar = RecordedTQDM.record[0] self.assert_play_pbar_correct_total_and_finished(play_pbar, total=15) # Check all progress bars are closed. @@ -355,7 +334,7 @@ def test_progress_bar_play(self): RecordedTQDM.reset_record() results = tournament.play(progress_bar=True) self.assertIsInstance(results, axelrod.ResultSet) - self.assertEqual(len(RecordedTQDM.record), 3) + self.assertEqual(len(RecordedTQDM.record), 2) play_pbar = RecordedTQDM.record[0] self.assert_play_pbar_correct_total_and_finished(play_pbar, total=15) @@ -368,8 +347,6 @@ def test_progress_bar_play(self): play_pbar = RecordedTQDM.record[0] self.assert_play_pbar_correct_total_and_finished(play_pbar, total=15) - results = axelrod.ResultSetFromFile(self.filename) - self.assertIsInstance(results, axelrod.ResultSet) @patch('tqdm.tqdm', RecordedTQDM) def test_progress_bar_play_parallel(self): @@ -393,7 +370,7 @@ def test_progress_bar_play_parallel(self): results = tournament.play(progress_bar=True, processes=2) self.assertIsInstance(results, axelrod.ResultSet) - self.assertEqual(len(RecordedTQDM.record), 3) + self.assertEqual(len(RecordedTQDM.record), 2) play_pbar = RecordedTQDM.record[0] self.assert_play_pbar_correct_total_and_finished(play_pbar, total=15) @@ -402,7 +379,7 @@ def test_progress_bar_play_parallel(self): results = tournament.play(processes=2) self.assertIsInstance(results, axelrod.ResultSet) - self.assertEqual(len(RecordedTQDM.record), 3) + self.assertEqual(len(RecordedTQDM.record), 2) play_pbar = RecordedTQDM.record[0] self.assert_play_pbar_correct_total_and_finished(play_pbar, total=15) @@ -432,7 +409,7 @@ def test_property_serial_play(self, tournament): # Test that we get an instance of ResultSet results = tournament.play(progress_bar=False) self.assertIsInstance(results, axelrod.ResultSet) - self.assertEqual(results.nplayers, len(tournament.players)) + self.assertEqual(results.num_players, len(tournament.players)) self.assertEqual(results.players, [str(p) for p in tournament.players]) def test_parallel_play(self): @@ -480,12 +457,12 @@ def test_run_serial(self): game=self.game, turns=axelrod.DEFAULT_TURNS, repetitions=self.test_repetitions) - tournament._write_interactions = MagicMock( - name='_write_interactions') + tournament._write_interactions_to_file = MagicMock( + name='_write_interactions_to_file') self.assertTrue(tournament._run_serial()) # Get the calls made to write_interactions - calls = tournament._write_interactions.call_args_list + calls = tournament._write_interactions_to_file.call_args_list self.assertEqual(len(calls), 15) def test_run_parallel(self): @@ -499,20 +476,20 @@ def __reduce__(self): game=self.game, turns=axelrod.DEFAULT_TURNS, repetitions=self.test_repetitions) - tournament._write_interactions = PickleableMock( - name='_write_interactions') + tournament._write_interactions_to_file = PickleableMock( + name='_write_interactions_to_file') # For test coverage purposes. This confirms PickleableMock can be # pickled exactly once. Windows multi-processing must pickle this Mock # exactly once during testing. pickled = pickle.loads(pickle.dumps(tournament)) - self.assertIsInstance(pickled._write_interactions, MagicMock) + self.assertIsInstance(pickled._write_interactions_to_file, MagicMock) self.assertRaises(pickle.PicklingError, pickle.dumps, pickled) self.assertTrue(tournament._run_parallel()) # Get the calls made to write_interactions - calls = tournament._write_interactions.call_args_list + calls = tournament._write_interactions_to_file.call_args_list self.assertEqual(len(calls), 15) def test_n_workers(self): @@ -608,10 +585,6 @@ def test_build_result_set(self): results = tournament.play(progress_bar=False) self.assertIsInstance(results, axelrod.ResultSet) - # Test in memory - results = tournament.play(progress_bar=False, in_memory=True) - self.assertIsInstance(results, axelrod.ResultSet) - def test_no_build_result_set(self): tournament = axelrod.Tournament( name=self.test_name, @@ -620,13 +593,15 @@ def test_no_build_result_set(self): turns=axelrod.DEFAULT_TURNS, repetitions=self.test_repetitions) - results = tournament.play(build_results=False, filename=self.filename, - progress_bar=False) - self.assertIsNone(results) + tournament._calculate_results = MagicMock(name='_calculate_results') + # Mocking this as it is called by play + self.assertIsNone(tournament.play(filename=self.filename, + progress_bar=False, + build_results=False)) - # Checking that results were written properly - results = axelrod.ResultSetFromFile(self.filename, progress_bar=False) - self.assertIsInstance(results, axelrod.ResultSet) + # Get the calls made to write_interactions + calls = tournament._calculate_results.call_args_list + self.assertEqual(len(calls), 0) @given(turns=integers(min_value=1, max_value=200)) @settings(max_examples=5, max_iterations=20) @@ -663,8 +638,9 @@ def make_chunk_generator(): # Check that have the expected number of repetitions self.assertEqual(len(plays), self.test_repetitions) for repetition in plays: - # Check that have the correct length for each rep - self.assertEqual(len(repetition), turns) + actions, results = repetition + self.assertEqual(len(actions), turns) + self.assertEqual(len(results), 10) # Check that matches no longer exist self.assertEqual((len(list(chunk_generator))), 0) @@ -691,26 +667,17 @@ def test_write_interactions(self): game=self.game, turns=2, repetitions=2) - tournament._write_interactions = MagicMock(name='_write_interactions') + tournament._write_interactions_to_file = MagicMock(name='_write_interactions_to_file') # Mocking this as it is called by play - tournament._build_result_set = MagicMock(name='_build_result_set') - self.assertTrue(tournament.play(filename=self.filename, - progress_bar=False)) - - # Get the calls made to write_interactions - calls = tournament._write_interactions.call_args_list - self.assertEqual(len(calls), 15) + self.assertIsNone(tournament.play(filename=self.filename, + progress_bar=False, + build_results=False)) - # Test when running in memory - tournament._write_interactions = MagicMock(name='_write_interactions') - self.assertTrue(tournament.play(filename=self.filename, - progress_bar=False, - in_memory=False)) # Get the calls made to write_interactions - calls = tournament._write_interactions.call_args_list + calls = tournament._write_interactions_to_file.call_args_list self.assertEqual(len(calls), 15) - def test_write_to_csv(self): + def test_write_to_csv_with_results(self): tournament = axelrod.Tournament( name=self.test_name, players=self.players, @@ -718,39 +685,20 @@ def test_write_to_csv(self): turns=2, repetitions=2) tournament.play(filename=self.filename, progress_bar=False) - with open(self.filename, 'r') as f: - written_data = [[int(r[0]), int(r[1])] + r[2:] for r in csv.reader(f)] - expected_data = [[0, 1, 'Cooperator', 'Tit For Tat', 'CC', 'CC'], - [0, 1, 'Cooperator', 'Tit For Tat', 'CC', 'CC'], - [1, 2, 'Tit For Tat', 'Defector', 'CD', 'DD'], - [1, 2, 'Tit For Tat', 'Defector', 'CD', 'DD'], - [0, 0, 'Cooperator', 'Cooperator', 'CC', 'CC'], - [0, 0, 'Cooperator', 'Cooperator', 'CC', 'CC'], - [3, 3, 'Grudger', 'Grudger', 'CC', 'CC'], - [3, 3, 'Grudger', 'Grudger', 'CC', 'CC'], - [2, 2, 'Defector', 'Defector', 'DD', 'DD'], - [2, 2, 'Defector', 'Defector', 'DD', 'DD'], - [4, 4, 'Soft Go By Majority', 'Soft Go By Majority', 'CC', 'CC'], - [4, 4, 'Soft Go By Majority', 'Soft Go By Majority', 'CC', 'CC'], - [1, 4, 'Tit For Tat', 'Soft Go By Majority', 'CC', 'CC'], - [1, 4, 'Tit For Tat', 'Soft Go By Majority', 'CC', 'CC'], - [1, 1, 'Tit For Tat', 'Tit For Tat', 'CC', 'CC'], - [1, 1, 'Tit For Tat', 'Tit For Tat', 'CC', 'CC'], - [1, 3, 'Tit For Tat', 'Grudger', 'CC', 'CC'], - [1, 3, 'Tit For Tat', 'Grudger', 'CC', 'CC'], - [2, 3, 'Defector', 'Grudger', 'DD', 'CD'], - [2, 3, 'Defector', 'Grudger', 'DD', 'CD'], - [0, 4, 'Cooperator', 'Soft Go By Majority', 'CC', 'CC'], - [0, 4, 'Cooperator', 'Soft Go By Majority', 'CC', 'CC'], - [2, 4, 'Defector', 'Soft Go By Majority', 'DD', 'CD'], - [2, 4, 'Defector', 'Soft Go By Majority', 'DD', 'CD'], - [0, 3, 'Cooperator', 'Grudger', 'CC', 'CC'], - [0, 3, 'Cooperator', 'Grudger', 'CC', 'CC'], - [3, 4, 'Grudger', 'Soft Go By Majority', 'CC', 'CC'], - [3, 4, 'Grudger', 'Soft Go By Majority', 'CC', 'CC'], - [0, 2, 'Cooperator', 'Defector', 'CC', 'DD'], - [0, 2, 'Cooperator', 'Defector', 'CC', 'DD']] - self.assertEqual(sorted(written_data), sorted(expected_data)) + self.assertTrue(filecmp.cmp(self.filename, + "test_outputs/expected_test_tournament.csv")) + + def test_write_to_csv_without_results(self): + tournament = axelrod.Tournament( + name=self.test_name, + players=self.players, + game=self.game, + turns=2, + repetitions=2) + tournament.play(filename=self.filename, progress_bar=False, + build_results=False) + self.assertTrue(filecmp.cmp(self.filename, + "test_outputs/expected_test_tournament_no_results.csv")) class TestProbEndTournament(unittest.TestCase): @@ -806,7 +754,7 @@ def test_property_serial_play(self, tournament): # Test that we get an instance of ResultSet results = tournament.play(progress_bar=False) self.assertIsInstance(results, axelrod.ResultSet) - self.assertEqual(results.nplayers, len(tournament.players)) + self.assertEqual(results.num_players, len(tournament.players)) self.assertEqual(results.players, [str(p) for p in tournament.players]) @@ -877,7 +825,7 @@ def test_complete_tournament(self, strategies, turns, repetitions, spatial_results = spatial_tournament.play(progress_bar=False) self.assertEqual(results.ranked_names, spatial_results.ranked_names) - self.assertEqual(results.nplayers, spatial_results.nplayers) + self.assertEqual(results.num_players, spatial_results.num_players) self.assertEqual(results.repetitions, spatial_results.repetitions) self.assertEqual(results.payoff_diffs_means, spatial_results.payoff_diffs_means) diff --git a/axelrod/tournament.py b/axelrod/tournament.py index 1c541d825..5e51a6e2e 100644 --- a/axelrod/tournament.py +++ b/axelrod/tournament.py @@ -14,7 +14,12 @@ from .game import Game from .match import Match from .match_generator import MatchGenerator -from .result_set import ResultSetFromFile, ResultSet +from .result_set import ResultSet +from axelrod.action import Action, str_to_actions + +import axelrod.interaction_utils as iu + +C, D = Action.C, Action.D from typing import List, Tuple @@ -81,23 +86,20 @@ def __init__(self, players: List[Player], self.filename = None # type: str self._temp_file_descriptor = None # type: int - def setup_output(self, filename=None, in_memory=False): + def setup_output(self, filename=None): """assign/create `filename` to `self`. If file should be deleted once `play` is finished, assign a file descriptor. """ temp_file_descriptor = None - if in_memory: - self.interactions_dict = {} - filename = None - if not in_memory and filename is None: + if filename is None: temp_file_descriptor, filename = mkstemp() self.filename = filename self._temp_file_descriptor = temp_file_descriptor + def play(self, build_results: bool = True, filename: str = None, processes: int = None, progress_bar: bool = True, - keep_interactions: bool = False, in_memory: bool = False - ) -> ResultSetFromFile: + ) -> ResultSet: """ Plays the tournament and passes the results to the ResultSet class @@ -111,22 +113,16 @@ def play(self, build_results: bool = True, filename: str = None, The number of processes to be used for parallel processing progress_bar : bool Whether or not to create a progress bar which will be updated - keep_interactions : bool - Whether or not to load the interactions in to memory - in_memory : bool - By default interactions are written to a file. - If this is True they will be kept in memory. - This is not advised for large tournaments. Returns ------- - axelrod.ResultSetFromFile + axelrod.ResultSet """ self.num_interactions = 0 self.use_progress_bar = progress_bar - self.setup_output(filename, in_memory) + self.setup_output(filename) if not build_results and not filename: warnings.warn( @@ -134,60 +130,36 @@ def play(self, build_results: bool = True, filename: str = None, "build_results=False and no filename was supplied.") if processes is None: - self._run_serial() + self._run_serial(build_results=build_results) else: - self._run_parallel(processes=processes) + self._run_parallel(build_results=build_results, processes=processes) result_set = None if build_results: - result_set = self._build_result_set( - keep_interactions=keep_interactions, in_memory=in_memory - ) - + result_set = ResultSet( + filename=self.filename, + players=[str(p) for p in self.players], + repetitions=self.repetitions, + processes=processes, + progress_bar=progress_bar) if self._temp_file_descriptor is not None: os.close(self._temp_file_descriptor) os.remove(self.filename) return result_set - def _build_result_set(self, keep_interactions: bool = False, - in_memory: bool = False): - """ - Build the result set (used by the play method) - Returns - ------- - axelrod.BigResultSet - """ - if not in_memory: - result_set = ResultSetFromFile( - filename=self.filename, - progress_bar=self.use_progress_bar, - num_interactions=self.num_interactions, - repetitions=self.repetitions, - players=[str(p) for p in self.players], - keep_interactions=keep_interactions, - game=self.game) - else: - result_set = ResultSet( - players=[str(p) for p in self.players], - interactions=self.interactions_dict, - repetitions=self.repetitions, - progress_bar=self.use_progress_bar, - game=self.game) - return result_set - - def _run_serial(self) -> bool: + def _run_serial(self, build_results: bool=True) -> bool: """Run all matches in serial.""" chunks = self.match_generator.build_match_chunks() - out_file, writer = self._get_file_objects() + out_file, writer = self._get_file_objects(build_results) progress_bar = self._get_progress_bar() for chunk in chunks: - results = self._play_matches(chunk) - self._write_interactions(results, writer=writer) + results = self._play_matches(chunk, build_results=build_results) + self._write_interactions_to_file(results, writer=writer) if self.use_progress_bar: progress_bar.update(1) @@ -196,7 +168,7 @@ def _run_serial(self) -> bool: return True - def _get_file_objects(self): + def _get_file_objects(self, build_results=True): """Returns the file object and writer for writing results or (None, None) if self.filename is None""" file_obj = None @@ -204,6 +176,38 @@ def _get_file_objects(self): if self.filename is not None: file_obj = open(self.filename, 'w') writer = csv.writer(file_obj, lineterminator='\n') + + header = ["Interaction index", + "Player index", + "Opponent index", + "Repetition", + "Player name", + "Opponent name", + "Actions"] + if build_results: + header.extend(["Score", + "Score difference", + "Turns", + "Score per turn", + "Score difference per turn", + "Win", + "Initial cooperation", + "Cooperation count", + "CC count", + "CD count", + "DC count", + "DD count", + "CC to C count", + "CC to D count", + "CD to C count", + "CD to D count", + "DC to C count", + "DC to D count", + "DD to C count", + "DD to D count", + "Good partner"]) + + writer.writerow(header) return file_obj, writer def _get_progress_bar(self): @@ -212,38 +216,59 @@ def _get_progress_bar(self): desc="Playing matches") return None - def _write_interactions(self, results, writer=None): - """Write the interactions to file or to a dictionary""" - if writer is not None: - self._write_interactions_to_file(results, writer) - elif self.interactions_dict is not None: - self._write_interactions_to_dict(results) - def _write_interactions_to_file(self, results, writer): """Write the interactions to csv.""" for index_pair, interactions in results.items(): - for interaction in interactions: - row = list(index_pair) - row.append(str(self.players[index_pair[0]])) - row.append(str(self.players[index_pair[1]])) - history1 = actions_to_str([i[0] for i in interaction]) - history2 = actions_to_str([i[1] for i in interaction]) - row.append(history1) - row.append(history2) - writer.writerow(row) + repetition = 0 + for interaction, results in interactions: + + if results is not None: + (scores, + score_diffs, + turns, score_per_turns, + score_diffs_per_turns, + initial_cooperation, + cooperations, + state_distribution, + state_to_action_distributions, + winner_index) = results + for index, player_index in enumerate(index_pair): + opponent_index = index_pair[index - 1] + row = [self.num_interactions, player_index, opponent_index, + repetition] + row.append(str(self.players[player_index])) + row.append(str(self.players[opponent_index])) + history = actions_to_str([i[index] for i in interaction]) + row.append(history) + + if results is not None: + row.append(scores[index]) + row.append(score_diffs[index]) + row.append(turns) + row.append(score_per_turns[index]) + row.append(score_diffs_per_turns[index]) + row.append(int(winner_index is index)) + row.append(initial_cooperation[index]) + row.append(cooperations[index]) + + states = [(C, C), (C, D), (D, C), (D, D)] + for state in states: + if index == 1: + state = state[::-1] + row.append(state_distribution[state]) + for state in states: + if index == 1: + state = state[::-1] + row.append(state_to_action_distributions[index][(state, C)]) + row.append(state_to_action_distributions[index][(state, D)]) + + row.append(int(cooperations[index] >= cooperations[index - 1])) + + writer.writerow(row) + repetition += 1 self.num_interactions += 1 - def _write_interactions_to_dict(self, results): - """Write the interactions to memory""" - for index_pair, interactions in results.items(): - for interaction in interactions: - try: - self.interactions_dict[index_pair].append(interaction) - except KeyError: - self.interactions_dict[index_pair] = [interaction] - self.num_interactions += 1 - - def _run_parallel(self, processes: int=2) -> bool: + def _run_parallel(self, processes: int=2, build_results: bool=True) -> bool: """ Run all matches in parallel @@ -263,8 +288,8 @@ def _run_parallel(self, processes: int=2) -> bool: for chunk in chunks: work_queue.put(chunk) - self._start_workers(workers, work_queue, done_queue) - self._process_done_queue(workers, done_queue) + self._start_workers(workers, work_queue, done_queue, build_results) + self._process_done_queue(workers, done_queue, build_results) return True @@ -283,7 +308,7 @@ def _n_workers(self, processes: int = 2) -> int: return n_workers def _start_workers(self, workers: int, work_queue: Queue, - done_queue: Queue) -> bool: + done_queue: Queue, build_results: bool=True) -> bool: """ Initiates the sub-processes to carry out parallel processing. @@ -298,12 +323,13 @@ def _start_workers(self, workers: int, work_queue: Queue, """ for worker in range(workers): process = Process( - target=self._worker, args=(work_queue, done_queue)) + target=self._worker, args=(work_queue, done_queue, build_results)) work_queue.put('STOP') process.start() return True - def _process_done_queue(self, workers: int, done_queue: Queue): + def _process_done_queue(self, workers: int, done_queue: Queue, + build_results: bool=True): """ Retrieves the matches from the parallel sub-processes @@ -314,7 +340,7 @@ def _process_done_queue(self, workers: int, done_queue: Queue): done_queue : multiprocessing.Queue A queue containing the output dictionaries from each round robin """ - out_file, writer = self._get_file_objects() + out_file, writer = self._get_file_objects(build_results) progress_bar = self._get_progress_bar() stops = 0 @@ -323,7 +349,7 @@ def _process_done_queue(self, workers: int, done_queue: Queue): if results == 'STOP': stops += 1 else: - self._write_interactions(results, writer) + self._write_interactions_to_file(results, writer) if self.use_progress_bar: progress_bar.update(1) @@ -331,7 +357,8 @@ def _process_done_queue(self, workers: int, done_queue: Queue): _close_objects(out_file, progress_bar) return True - def _worker(self, work_queue: Queue, done_queue: Queue): + def _worker(self, work_queue: Queue, done_queue: Queue, + build_results: bool=True): """ The work for each parallel sub-process to execute. @@ -343,12 +370,12 @@ def _worker(self, work_queue: Queue, done_queue: Queue): A queue containing the output dictionaries from each round robin """ for chunk in iter(work_queue.get, 'STOP'): - interactions = self._play_matches(chunk) + interactions = self._play_matches(chunk, build_results) done_queue.put(interactions) done_queue.put('STOP') return True - def _play_matches(self, chunk): + def _play_matches(self, chunk, build_results=True): """ Play matches in a given chunk. @@ -373,9 +400,53 @@ def _play_matches(self, chunk): match = Match(**match_params) for _ in range(repetitions): match.play() - interactions[index_pair].append(match.result) + + if build_results: + results = self._calculate_results(match.result) + else: + results = None + + interactions[index_pair].append([match.result, results]) return interactions + def _calculate_results(self, interactions): + results = [] + + scores = iu.compute_final_score(interactions, self.game) + results.append(scores) + + score_diffs = scores[0] - scores[1], scores[1] - scores[0] + results.append(score_diffs) + + turns = len(interactions) + results.append(turns) + + score_per_turns = iu.compute_final_score_per_turn(interactions, + self.game) + results.append(score_per_turns) + + score_diffs_per_turns = score_diffs[0] / turns, score_diffs[1] / turns + results.append(score_diffs_per_turns) + + initial_coops = tuple(map( + bool, + iu.compute_cooperations(interactions[:1]))) + results.append(initial_coops) + + cooperations = iu.compute_cooperations(interactions) + results.append(cooperations) + + state_distribution = iu.compute_state_distribution(interactions) + results.append(state_distribution) + + state_to_action_distributions = iu.compute_state_to_action_distribution(interactions) + results.append(state_to_action_distributions) + + winner_index = iu.compute_winner_index(interactions, self.game) + results.append(winner_index) + + return results + def _close_objects(*objs): """If the objects have a `close` method, closes them.""" diff --git a/docs/tutorials/advanced/reading_and_writing_interactions.rst b/docs/tutorials/advanced/reading_and_writing_interactions.rst index fec2813e6..7552fea7f 100644 --- a/docs/tutorials/advanced/reading_and_writing_interactions.rst +++ b/docs/tutorials/advanced/reading_and_writing_interactions.rst @@ -117,17 +117,3 @@ within the library. Note that you can supply `build_results=False` as a keyword argument to `tournament.play()` to prevent keeping or loading interactions in memory, since the total memory footprint can be large for various combinations of parameters. The memory usage scales as :math:`O(\text{players}^2 * \text{turns} * \text{repetitions})`. - -It is also possible to generate a standard result set from a datafile:: - - >>> results = axl.ResultSetFromFile(filename="basic_tournament.csv") - >>> results.ranked_names # doctest: +SKIP - ['Defector', - 'Bully', - 'Suspicious Tit For Tat', - 'Alternator', - 'Tit For Tat', - 'Anti Tit For Tat', - 'Win-Stay Lose-Shift', - 'Cooperator'] - diff --git a/docs/tutorials/advanced/tournament_results.rst b/docs/tutorials/advanced/tournament_results.rst index e94156e9b..f2778ebfa 100644 --- a/docs/tutorials/advanced/tournament_results.rst +++ b/docs/tutorials/advanced/tournament_results.rst @@ -65,9 +65,19 @@ This gives the length of the matches played by each player:: >>> import pprint # Nicer formatting of output >>> pprint.pprint(results.match_lengths) - [[[10, 10, 10, 10], [10, 10, 10, 10], [10, 10, 10, 10], [10, 10, 10, 10]], - [[10, 10, 10, 10], [10, 10, 10, 10], [10, 10, 10, 10], [10, 10, 10, 10]], - [[10, 10, 10, 10], [10, 10, 10, 10], [10, 10, 10, 10], [10, 10, 10, 10]]] + [[[10.0, 10.0, 10.0, 10.0], + [10.0, 10.0, 10.0, 10.0], + [10.0, 10.0, 10.0, 10.0], + [10.0, 10.0, 10.0, 10.0]], + [[10.0, 10.0, 10.0, 10.0], + [10.0, 10.0, 10.0, 10.0], + [10.0, 10.0, 10.0, 10.0], + [10.0, 10.0, 10.0, 10.0]], + [[10.0, 10.0, 10.0, 10.0], + [10.0, 10.0, 10.0, 10.0], + [10.0, 10.0, 10.0, 10.0], + [10.0, 10.0, 10.0, 10.0]]] + Every player plays 10 turns against every other player (including themselves) for every repetition of the tournament. @@ -119,7 +129,7 @@ Payoffs This gives for each player, against each opponent every payoff received for each repetition:: - >>> pprint.pprint(results.payoffs) + >>> pprint.pprint(results.payoffs) # doctest: +SKIP [[[3.0, 3.0, 3.0], [0.0, 0.0, 0.0], [3.0, 3.0, 3.0], [3.0, 3.0, 3.0]], [[5.0, 5.0, 5.0], [1.0, 1.0, 1.0], [1.4, 1.4, 1.4], [1.4, 1.4, 1.4]], [[3.0, 3.0, 3.0], [0.9, 0.9, 0.9], [3.0, 3.0, 3.0], [3.0, 3.0, 3.0]], @@ -202,7 +212,7 @@ Cooperation counts This gives a total count of cooperation for each player against each opponent:: >>> results.cooperation - [[0, 30, 30, 30], [0, 0, 0, 0], [30, 3, 0, 30], [30, 3, 30, 0]] + [[30, 30, 30, 30], [0, 0, 0, 0], [30, 3, 30, 30], [30, 3, 30, 30]] Normalised cooperation ---------------------- @@ -262,14 +272,14 @@ the second the action of the opponent:: Counter({(C, C): 1.0})], [Counter({(D, C): 1.0}), Counter(), - Counter({(D, D): 0.9, (D, C): 0.1}), - Counter({(D, D): 0.9, (D, C): 0.1})], + Counter({(D, D): 0.9..., (D, C): 0.1...}), + Counter({(D, D): 0.9..., (D, C): 0.1...})], [Counter({(C, C): 1.0}), - Counter({(D, D): 0.9, (C, D): 0.1}), + Counter({(D, D): 0.9..., (C, D): 0.1...}), Counter(), Counter({(C, C): 1.0})], [Counter({(C, C): 1.0}), - Counter({(D, D): 0.9, (C, D): 0.1}), + Counter({(D, D): 0.9..., (C, D): 0.1...}), Counter({(C, C): 1.0}), Counter()]] @@ -333,7 +343,7 @@ This gives the count of cooperations made by each player during the first turn of every match:: >>> results.initial_cooperation_count - [9, 0, 9, 9] + [9.0, 0.0, 9.0, 9.0] Each player plays an opponent a total of 9 times (3 opponents and 3 repetitions). Apart from the :code:`Defector`, they all cooperate on the first @@ -353,7 +363,7 @@ Morality Metrics The following morality metrics are available, they are calculated as a function of the cooperation rating:: - >>> results.cooperating_rating + >>> results.cooperating_rating # doctest: +SKIP [1.0, 0.0, 0.7, 0.7] >>> pprint.pprint(results.vengeful_cooperation) # doctest: +SKIP [[1.0, 1.0, 1.0, 1.0], diff --git a/docs/tutorials/further_topics/morality_metrics.rst b/docs/tutorials/further_topics/morality_metrics.rst index 11bfcf040..090314074 100644 --- a/docs/tutorials/further_topics/morality_metrics.rst +++ b/docs/tutorials/further_topics/morality_metrics.rst @@ -38,7 +38,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.67, 0.67] + [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/spatial_tournaments.rst b/docs/tutorials/further_topics/spatial_tournaments.rst index 8e922adf1..3bb369ab2 100644 --- a/docs/tutorials/further_topics/spatial_tournaments.rst +++ b/docs/tutorials/further_topics/spatial_tournaments.rst @@ -30,7 +30,7 @@ To create a spatial tournament you pass the :code:`edges` to the :code:`Tournament` class:: >>> spatial_tournament = axl.Tournament(players, edges=edges) - >>> results = spatial_tournament.play(keep_interactions=True) + >>> results = spatial_tournament.play() We can plot the results:: @@ -47,33 +47,23 @@ We can, like any other tournament, obtain the ranks for our players:: >>> results.ranked_names ['Cooperator', 'Tit For Tat', 'Grudger', 'Defector'] -Let's run a small tournament of 2 :code:`turns` and 5 :code:`repetitions` +Let's run a small tournament of 2 :code:`turns` and 2 :code:`repetitions` and obtain the interactions:: >>> spatial_tournament = axl.Tournament(players ,turns=2, repetitions=2, edges=edges) - >>> results = spatial_tournament.play(keep_interactions=True) - >>> for index_pair, interaction in sorted(results.interactions.items()): - ... player1 = spatial_tournament.players[index_pair[0]] - ... player2 = spatial_tournament.players[index_pair[1]] - ... print('%s vs %s: %s' % (player1, player2, interaction)) - Cooperator vs Tit For Tat: [[(C, C), (C, C)], [(C, C), (C, C)]] - Cooperator vs Grudger: [[(C, C), (C, C)], [(C, C), (C, C)]] - Defector vs Tit For Tat: [[(D, C), (D, D)], [(D, C), (D, D)]] - Defector vs Grudger: [[(D, C), (D, D)], [(D, C), (D, D)]] - -As anticipated :code:`Cooperator` does not interact with :code:`Defector` neither -:code:`TitForTat` with :code:`Grudger`. + >>> results = spatial_tournament.play() + >>> results.payoffs + [[[], [], [3.0, 3.0], [3.0, 3.0]], [[], [], [3.0, 3.0], [3.0, 3.0]], [[3.0, 3.0], [0.5, 0.5], [], []], [[3.0, 3.0], [0.5, 0.5], [], []]] + +As anticipated not all players interact with each other. It is also possible to create a probabilistic ending spatial tournament:: >>> prob_end_spatial_tournament = axl.Tournament(players, edges=edges, prob_end=.1, repetitions=1) - >>> prob_end_results = prob_end_spatial_tournament.play(keep_interactions=True) + >>> axl.seed(0) + >>> prob_end_results = prob_end_spatial_tournament.play() We see that the match lengths are no longer all equal:: - >>> axl.seed(0) - >>> lengths = [] - >>> for interaction in prob_end_results.interactions.values(): - ... lengths.append(len(interaction[0])) - >>> min(lengths) != max(lengths) - True + >>> prob_end_results.match_lengths + [[[0, 0, 18.0, 14.0], [0, 0, 6.0, 3.0], [18.0, 6.0, 0, 0], [14.0, 3.0, 0, 0]]] diff --git a/docs/tutorials/getting_started/index.rst b/docs/tutorials/getting_started/index.rst index 83843e140..8186ed97a 100644 --- a/docs/tutorials/getting_started/index.rst +++ b/docs/tutorials/getting_started/index.rst @@ -13,7 +13,6 @@ Contents: match.rst tournament.rst summarising_tournaments.rst - interactions.rst visualising_results.rst moran.rst human_interaction.rst diff --git a/docs/tutorials/getting_started/interactions.rst b/docs/tutorials/getting_started/interactions.rst deleted file mode 100644 index d820655fa..000000000 --- a/docs/tutorials/getting_started/interactions.rst +++ /dev/null @@ -1,66 +0,0 @@ -Accessing the interactions -========================== - -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:`creating_tournaments`) but indicate that we want to keep track of the -interactions:: - - >>> import axelrod as axl - >>> players = [ - ... axl.Cooperator(), axl.Defector(), - ... axl.TitForTat(), axl.Grudger()] - >>> tournament = axl.Tournament(players, turns=3, repetitions=1) - >>> results = tournament.play(keep_interactions=True) - -If the play method is called with :code:`keep_interactions=True`, the result set -object will have an :code:`interactions` attribute which contains all the -interactions between the players. These can be used to -view the history of the interactions:: - - >>> for index_pair, interaction in sorted(results.interactions.items()): - ... player1 = tournament.players[index_pair[0]] - ... player2 = tournament.players[index_pair[1]] - ... print('%s vs %s: %s' % (player1, player2, interaction[0])) - Cooperator vs Cooperator: [(C, C), (C, C), (C, C)] - Cooperator vs Defector: [(C, D), (C, D), (C, D)] - Cooperator vs Tit For Tat: [(C, C), (C, C), (C, C)] - Cooperator vs Grudger: [(C, C), (C, C), (C, C)] - Defector vs Defector: [(D, D), (D, D), (D, D)] - Defector vs Tit For Tat: [(D, C), (D, D), (D, D)] - Defector vs Grudger: [(D, C), (D, D), (D, D)] - Tit For Tat vs Tit For Tat: [(C, C), (C, C), (C, C)] - Tit For Tat vs Grudger: [(C, C), (C, C), (C, C)] - Grudger vs Grudger: [(C, C), (C, C), (C, C)] - -We can use these interactions to reconstruct :code:`axelrod.Match` objects which -have a variety of available methods for analysis (more information can be found -in :ref:`creating_matches`):: - - >>> matches = [] - >>> for index_pair, interaction in sorted(results.interactions.items()): - ... player1 = tournament.players[index_pair[0]] - ... player2 = tournament.players[index_pair[1]] - ... match = axl.Match([player1, player2], turns=3) - ... match.result = interaction[0] - ... matches.append(match) - >>> len(matches) - 10 - -As an example let us view all winners of each match (:code:`False` indicates a -tie):: - - >>> for match in matches: - ... print("{} v {}, winner: {}".format(match.players[0], match.players[1], match.winner())) - Cooperator v Cooperator, winner: False - Cooperator v Defector, winner: Defector - Cooperator v Tit For Tat, winner: False - Cooperator v Grudger, winner: False - Defector v Defector, winner: False - Defector v Tit For Tat, winner: Defector - Defector v Grudger, winner: Defector - Tit For Tat v Tit For Tat, winner: False - Tit For Tat v Grudger, winner: False - Grudger v Grudger, winner: False diff --git a/docs/tutorials/getting_started/summarising_tournaments.rst b/docs/tutorials/getting_started/summarising_tournaments.rst index 0314dae88..ebfb39b08 100644 --- a/docs/tutorials/getting_started/summarising_tournaments.rst +++ b/docs/tutorials/getting_started/summarising_tournaments.rst @@ -19,8 +19,8 @@ that summarises the results of the tournament:: >>> import pprint >>> pprint.pprint(summary) [Player(Rank=0, Name='Defector', Median_score=2.6..., Cooperation_rating=0.0, Wins=3.0, Initial_C_rate=0.0, CC_rate=...), - Player(Rank=1, Name='Tit For Tat', Median_score=2.3..., Cooperation_rating=0.7, Wins=0.0, Initial_C_rate=1.0, CC_rate=...), - Player(Rank=2, Name='Grudger', Median_score=2.3..., Cooperation_rating=0.7, Wins=0.0, Initial_C_rate=1.0, CC_rate=...), + Player(Rank=1, Name='Tit For Tat', Median_score=2.3..., Cooperation_rating=0..., Wins=0.0, Initial_C_rate=1.0, CC_rate=...), + Player(Rank=2, Name='Grudger', Median_score=2.3..., Cooperation_rating=0..., Wins=0.0, Initial_C_rate=1.0, CC_rate=...), Player(Rank=3, Name='Cooperator', Median_score=2.0..., Cooperation_rating=1.0, Wins=0.0, Initial_C_rate=1.0, CC_rate=...)] It is also possible to write this data directly to a csv file using the diff --git a/requirements.txt b/requirements.txt index 1f9449d7e..bb7e66cb9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,5 @@ tqdm>=3.4.0 prompt-toolkit>=1.0.7 scipy>=0.19.0 hypothesis==3.2 +dask>=0.11.0 +pandas>=0.18.1 diff --git a/test_outputs/expected_test_tournament.csv b/test_outputs/expected_test_tournament.csv new file mode 100644 index 000000000..643161739 --- /dev/null +++ b/test_outputs/expected_test_tournament.csv @@ -0,0 +1,61 @@ +Interaction index,Player index,Opponent index,Repetition,Player name,Opponent name,Actions,Score,Score difference,Turns,Score per turn,Score difference per turn,Win,Initial cooperation,Cooperation count,CC count,CD count,DC count,DD count,CC to C count,CC to D count,CD to C count,CD to D count,DC to C count,DC to D count,DD to C count,DD to D count,Good partner +0,0,0,0,Cooperator,Cooperator,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +0,0,0,0,Cooperator,Cooperator,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +1,0,0,1,Cooperator,Cooperator,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +1,0,0,1,Cooperator,Cooperator,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +2,0,1,0,Cooperator,Tit For Tat,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +2,1,0,0,Tit For Tat,Cooperator,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +3,0,1,1,Cooperator,Tit For Tat,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +3,1,0,1,Tit For Tat,Cooperator,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +4,0,2,0,Cooperator,Defector,CC,0,-10,2,0.0,-5.0,0,True,2,0,2,0,0,0,0,1,0,0,0,0,0,1 +4,2,0,0,Defector,Cooperator,DD,10,10,2,5.0,5.0,1,False,0,0,0,2,0,0,0,0,0,0,1,0,0,0 +5,0,2,1,Cooperator,Defector,CC,0,-10,2,0.0,-5.0,0,True,2,0,2,0,0,0,0,1,0,0,0,0,0,1 +5,2,0,1,Defector,Cooperator,DD,10,10,2,5.0,5.0,1,False,0,0,0,2,0,0,0,0,0,0,1,0,0,0 +6,0,3,0,Cooperator,Grudger,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +6,3,0,0,Grudger,Cooperator,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +7,0,3,1,Cooperator,Grudger,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +7,3,0,1,Grudger,Cooperator,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +8,0,4,0,Cooperator,Soft Go By Majority,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +8,4,0,0,Soft Go By Majority,Cooperator,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +9,0,4,1,Cooperator,Soft Go By Majority,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +9,4,0,1,Soft Go By Majority,Cooperator,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +10,1,1,0,Tit For Tat,Tit For Tat,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +10,1,1,0,Tit For Tat,Tit For Tat,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +11,1,1,1,Tit For Tat,Tit For Tat,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +11,1,1,1,Tit For Tat,Tit For Tat,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +12,1,2,0,Tit For Tat,Defector,CD,1,-5,2,0.5,-2.5,0,True,1,0,1,0,1,0,0,0,1,0,0,0,0,1 +12,2,1,0,Defector,Tit For Tat,DD,6,5,2,3.0,2.5,1,False,0,0,0,1,1,0,0,0,0,0,1,0,0,0 +13,1,2,1,Tit For Tat,Defector,CD,1,-5,2,0.5,-2.5,0,True,1,0,1,0,1,0,0,0,1,0,0,0,0,1 +13,2,1,1,Defector,Tit For Tat,DD,6,5,2,3.0,2.5,1,False,0,0,0,1,1,0,0,0,0,0,1,0,0,0 +14,1,3,0,Tit For Tat,Grudger,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +14,3,1,0,Grudger,Tit For Tat,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +15,1,3,1,Tit For Tat,Grudger,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +15,3,1,1,Grudger,Tit For Tat,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +16,1,4,0,Tit For Tat,Soft Go By Majority,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +16,4,1,0,Soft Go By Majority,Tit For Tat,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +17,1,4,1,Tit For Tat,Soft Go By Majority,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +17,4,1,1,Soft Go By Majority,Tit For Tat,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +18,2,2,0,Defector,Defector,DD,2,0,2,1.0,0.0,0,False,0,0,0,0,2,0,0,0,0,0,0,0,1,1 +18,2,2,0,Defector,Defector,DD,2,0,2,1.0,0.0,0,False,0,0,0,0,2,0,0,0,0,0,0,0,1,1 +19,2,2,1,Defector,Defector,DD,2,0,2,1.0,0.0,0,False,0,0,0,0,2,0,0,0,0,0,0,0,1,1 +19,2,2,1,Defector,Defector,DD,2,0,2,1.0,0.0,0,False,0,0,0,0,2,0,0,0,0,0,0,0,1,1 +20,2,3,0,Defector,Grudger,DD,6,5,2,3.0,2.5,1,False,0,0,0,1,1,0,0,0,0,0,1,0,0,0 +20,3,2,0,Grudger,Defector,CD,1,-5,2,0.5,-2.5,0,True,1,0,1,0,1,0,0,0,1,0,0,0,0,1 +21,2,3,1,Defector,Grudger,DD,6,5,2,3.0,2.5,1,False,0,0,0,1,1,0,0,0,0,0,1,0,0,0 +21,3,2,1,Grudger,Defector,CD,1,-5,2,0.5,-2.5,0,True,1,0,1,0,1,0,0,0,1,0,0,0,0,1 +22,2,4,0,Defector,Soft Go By Majority,DD,6,5,2,3.0,2.5,1,False,0,0,0,1,1,0,0,0,0,0,1,0,0,0 +22,4,2,0,Soft Go By Majority,Defector,CD,1,-5,2,0.5,-2.5,0,True,1,0,1,0,1,0,0,0,1,0,0,0,0,1 +23,2,4,1,Defector,Soft Go By Majority,DD,6,5,2,3.0,2.5,1,False,0,0,0,1,1,0,0,0,0,0,1,0,0,0 +23,4,2,1,Soft Go By Majority,Defector,CD,1,-5,2,0.5,-2.5,0,True,1,0,1,0,1,0,0,0,1,0,0,0,0,1 +24,3,3,0,Grudger,Grudger,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +24,3,3,0,Grudger,Grudger,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +25,3,3,1,Grudger,Grudger,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +25,3,3,1,Grudger,Grudger,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +26,3,4,0,Grudger,Soft Go By Majority,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +26,4,3,0,Soft Go By Majority,Grudger,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +27,3,4,1,Grudger,Soft Go By Majority,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +27,4,3,1,Soft Go By Majority,Grudger,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +28,4,4,0,Soft Go By Majority,Soft Go By Majority,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +28,4,4,0,Soft Go By Majority,Soft Go By Majority,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +29,4,4,1,Soft Go By Majority,Soft Go By Majority,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 +29,4,4,1,Soft Go By Majority,Soft Go By Majority,CC,6,0,2,3.0,0.0,0,True,2,2,0,0,0,1,0,0,0,0,0,0,0,1 diff --git a/test_outputs/expected_test_tournament_no_results.csv b/test_outputs/expected_test_tournament_no_results.csv new file mode 100644 index 000000000..b44468a7b --- /dev/null +++ b/test_outputs/expected_test_tournament_no_results.csv @@ -0,0 +1,61 @@ +Interaction index,Player index,Opponent index,Repetition,Player name,Opponent name,Actions +0,0,0,0,Cooperator,Cooperator,CC +0,0,0,0,Cooperator,Cooperator,CC +1,0,0,1,Cooperator,Cooperator,CC +1,0,0,1,Cooperator,Cooperator,CC +2,0,1,0,Cooperator,Tit For Tat,CC +2,1,0,0,Tit For Tat,Cooperator,CC +3,0,1,1,Cooperator,Tit For Tat,CC +3,1,0,1,Tit For Tat,Cooperator,CC +4,0,2,0,Cooperator,Defector,CC +4,2,0,0,Defector,Cooperator,DD +5,0,2,1,Cooperator,Defector,CC +5,2,0,1,Defector,Cooperator,DD +6,0,3,0,Cooperator,Grudger,CC +6,3,0,0,Grudger,Cooperator,CC +7,0,3,1,Cooperator,Grudger,CC +7,3,0,1,Grudger,Cooperator,CC +8,0,4,0,Cooperator,Soft Go By Majority,CC +8,4,0,0,Soft Go By Majority,Cooperator,CC +9,0,4,1,Cooperator,Soft Go By Majority,CC +9,4,0,1,Soft Go By Majority,Cooperator,CC +10,1,1,0,Tit For Tat,Tit For Tat,CC +10,1,1,0,Tit For Tat,Tit For Tat,CC +11,1,1,1,Tit For Tat,Tit For Tat,CC +11,1,1,1,Tit For Tat,Tit For Tat,CC +12,1,2,0,Tit For Tat,Defector,CD +12,2,1,0,Defector,Tit For Tat,DD +13,1,2,1,Tit For Tat,Defector,CD +13,2,1,1,Defector,Tit For Tat,DD +14,1,3,0,Tit For Tat,Grudger,CC +14,3,1,0,Grudger,Tit For Tat,CC +15,1,3,1,Tit For Tat,Grudger,CC +15,3,1,1,Grudger,Tit For Tat,CC +16,1,4,0,Tit For Tat,Soft Go By Majority,CC +16,4,1,0,Soft Go By Majority,Tit For Tat,CC +17,1,4,1,Tit For Tat,Soft Go By Majority,CC +17,4,1,1,Soft Go By Majority,Tit For Tat,CC +18,2,2,0,Defector,Defector,DD +18,2,2,0,Defector,Defector,DD +19,2,2,1,Defector,Defector,DD +19,2,2,1,Defector,Defector,DD +20,2,3,0,Defector,Grudger,DD +20,3,2,0,Grudger,Defector,CD +21,2,3,1,Defector,Grudger,DD +21,3,2,1,Grudger,Defector,CD +22,2,4,0,Defector,Soft Go By Majority,DD +22,4,2,0,Soft Go By Majority,Defector,CD +23,2,4,1,Defector,Soft Go By Majority,DD +23,4,2,1,Soft Go By Majority,Defector,CD +24,3,3,0,Grudger,Grudger,CC +24,3,3,0,Grudger,Grudger,CC +25,3,3,1,Grudger,Grudger,CC +25,3,3,1,Grudger,Grudger,CC +26,3,4,0,Grudger,Soft Go By Majority,CC +26,4,3,0,Soft Go By Majority,Grudger,CC +27,3,4,1,Grudger,Soft Go By Majority,CC +27,4,3,1,Soft Go By Majority,Grudger,CC +28,4,4,0,Soft Go By Majority,Soft Go By Majority,CC +28,4,4,0,Soft Go By Majority,Soft Go By Majority,CC +29,4,4,1,Soft Go By Majority,Soft Go By Majority,CC +29,4,4,1,Soft Go By Majority,Soft Go By Majority,CC diff --git a/test_outputs/test_results.csv b/test_outputs/test_results.csv new file mode 100644 index 000000000..6055c18d4 --- /dev/null +++ b/test_outputs/test_results.csv @@ -0,0 +1,19 @@ +Interaction index,Player index,Opponent index,Repetition,Player name,Opponent name,Actions,Score,Score difference,Turns,Score per turn,Score difference per turn,Win,Initial cooperation,Cooperation count,CC count,CD count,DC count,DD count,CC to C count,CC to D count,CD to C count,CD to D count,DC to C count,DC to D count,DD to C count,DD to D count,Good partner +0,0,1,0,Alternator,Tit For Tat,CDCDC,13,0,5,2.6,0.0,0,True,3,1,2,2,0,0,1,0,1,2,0,0,0,1 +0,1,0,0,Tit For Tat,Alternator,CCDCD,13,0,5,2.6,0.0,0,True,3,1,2,2,0,1,0,0,2,1,0,0,0,1 +1,0,1,1,Alternator,Tit For Tat,CDCDC,13,0,5,2.6,0.0,0,True,3,1,2,2,0,0,1,0,1,2,0,0,0,1 +1,1,0,1,Tit For Tat,Alternator,CCDCD,13,0,5,2.6,0.0,0,True,3,1,2,2,0,1,0,0,2,1,0,0,0,1 +2,0,1,2,Alternator,Tit For Tat,CDCDC,13,0,5,2.6,0.0,0,True,3,1,2,2,0,0,1,0,1,2,0,0,0,1 +2,1,0,2,Tit For Tat,Alternator,CCDCD,13,0,5,2.6,0.0,0,True,3,1,2,2,0,1,0,0,2,1,0,0,0,1 +3,0,2,0,Alternator,Defector,CDCDC,2,-15,5,0.4,-3.0,0,True,3,0,3,0,2,0,0,0,2,0,0,2,0,1 +3,2,0,0,Defector,Alternator,DDDDD,17,15,5,3.4,3.0,1,False,0,0,0,3,2,0,0,0,0,0,2,0,2,0 +4,0,2,1,Alternator,Defector,CDCDC,2,-15,5,0.4,-3.0,0,True,3,0,3,0,2,0,0,0,2,0,0,2,0,1 +4,2,0,1,Defector,Alternator,DDDDD,17,15,5,3.4,3.0,1,False,0,0,0,3,2,0,0,0,0,0,2,0,2,0 +5,0,2,2,Alternator,Defector,CDCDC,2,-15,5,0.4,-3.0,0,True,3,0,3,0,2,0,0,0,2,0,0,2,0,1 +5,2,0,2,Defector,Alternator,DDDDD,17,15,5,3.4,3.0,1,False,0,0,0,3,2,0,0,0,0,0,2,0,2,0 +6,1,2,0,Tit For Tat,Defector,CDDDD,4,-5,5,0.8,-1.0,0,True,1,0,1,0,4,0,0,0,1,0,0,0,3,1 +6,2,1,0,Defector,Tit For Tat,DDDDD,9,5,5,1.8,1.0,1,False,0,0,0,1,4,0,0,0,0,0,1,0,3,0 +7,1,2,1,Tit For Tat,Defector,CDDDD,4,-5,5,0.8,-1.0,0,True,1,0,1,0,4,0,0,0,1,0,0,0,3,1 +7,2,1,1,Defector,Tit For Tat,DDDDD,9,5,5,1.8,1.0,1,False,0,0,0,1,4,0,0,0,0,0,1,0,3,0 +8,1,2,2,Tit For Tat,Defector,CDDDD,4,-5,5,0.8,-1.0,0,True,1,0,1,0,4,0,0,0,1,0,0,0,3,1 +8,2,1,2,Defector,Tit For Tat,DDDDD,9,5,5,1.8,1.0,1,False,0,0,0,1,4,0,0,0,0,0,1,0,3,0 diff --git a/test_outputs/test_results_spatial.csv b/test_outputs/test_results_spatial.csv new file mode 100644 index 000000000..45c352e53 --- /dev/null +++ b/test_outputs/test_results_spatial.csv @@ -0,0 +1,13 @@ +Interaction index,Player index,Opponent index,Repetition,Player name,Opponent name,Actions,Score,Score difference,Turns,Score per turn,Score difference per turn,Win,Initial cooperation,Cooperation count,CC count,CD count,DC count,DD count,CC to C count,CC to D count,CD to C count,CD to D count,DC to C count,DC to D count,DD to C count,DD to D count,Good partner +0,0,1,0,Alternator,Tit For Tat,CDCDC,13,0,5,2.6,0.0,0,True,3,1,2,2,0,0,1,0,1,2,0,0,0,1 +0,1,0,0,Tit For Tat,Alternator,CCDCD,13,0,5,2.6,0.0,0,True,3,1,2,2,0,1,0,0,2,1,0,0,0,1 +1,0,1,1,Alternator,Tit For Tat,CDCDC,13,0,5,2.6,0.0,0,True,3,1,2,2,0,0,1,0,1,2,0,0,0,1 +1,1,0,1,Tit For Tat,Alternator,CCDCD,13,0,5,2.6,0.0,0,True,3,1,2,2,0,1,0,0,2,1,0,0,0,1 +2,0,1,2,Alternator,Tit For Tat,CDCDC,13,0,5,2.6,0.0,0,True,3,1,2,2,0,0,1,0,1,2,0,0,0,1 +2,1,0,2,Tit For Tat,Alternator,CCDCD,13,0,5,2.6,0.0,0,True,3,1,2,2,0,1,0,0,2,1,0,0,0,1 +3,0,2,0,Alternator,Defector,CDCDC,2,-15,5,0.4,-3.0,0,True,3,0,3,0,2,0,0,0,2,0,0,2,0,1 +3,2,0,0,Defector,Alternator,DDDDD,17,15,5,3.4,3.0,1,False,0,0,0,3,2,0,0,0,0,0,2,0,2,0 +4,0,2,1,Alternator,Defector,CDCDC,2,-15,5,0.4,-3.0,0,True,3,0,3,0,2,0,0,0,2,0,0,2,0,1 +4,2,0,1,Defector,Alternator,DDDDD,17,15,5,3.4,3.0,1,False,0,0,0,3,2,0,0,0,0,0,2,0,2,0 +5,0,2,2,Alternator,Defector,CDCDC,2,-15,5,0.4,-3.0,0,True,3,0,3,0,2,0,0,0,2,0,0,2,0,1 +5,2,0,2,Defector,Alternator,DDDDD,17,15,5,3.4,3.0,1,False,0,0,0,3,2,0,0,0,0,0,2,0,2,0 diff --git a/test_outputs/test_results_spatial_three.csv b/test_outputs/test_results_spatial_three.csv new file mode 100644 index 000000000..a0c1b435f --- /dev/null +++ b/test_outputs/test_results_spatial_three.csv @@ -0,0 +1,25 @@ +Interaction index,Player index,Opponent index,Repetition,Player name,Opponent name,Actions,Score,Score difference,Turns,Score per turn,Score difference per turn,Win,Initial cooperation,Cooperation count,CC count,CD count,DC count,DD count,CC to C count,CC to D count,CD to C count,CD to D count,DC to C count,DC to D count,DD to C count,DD to D count,Good partner +0,0,0,0,Alternator,Alternator,CDCDC,11,0,5,2.2,0.0,0,True,3,3,0,0,2,0,2,0,0,0,0,2,0,1 +0,0,0,0,Alternator,Alternator,CDCDC,11,0,5,2.2,0.0,0,True,3,3,0,0,2,0,2,0,0,0,0,2,0,1 +1,0,0,1,Alternator,Alternator,CDCDC,11,0,5,2.2,0.0,0,True,3,3,0,0,2,0,2,0,0,0,0,2,0,1 +1,0,0,1,Alternator,Alternator,CDCDC,11,0,5,2.2,0.0,0,True,3,3,0,0,2,0,2,0,0,0,0,2,0,1 +2,0,0,2,Alternator,Alternator,CDCDC,11,0,5,2.2,0.0,0,True,3,3,0,0,2,0,2,0,0,0,0,2,0,1 +2,0,0,2,Alternator,Alternator,CDCDC,11,0,5,2.2,0.0,0,True,3,3,0,0,2,0,2,0,0,0,0,2,0,1 +3,1,1,0,Tit For Tat,Tit For Tat,CCCCC,15,0,5,3.0,0.0,0,True,5,5,0,0,0,4,0,0,0,0,0,0,0,1 +3,1,1,0,Tit For Tat,Tit For Tat,CCCCC,15,0,5,3.0,0.0,0,True,5,5,0,0,0,4,0,0,0,0,0,0,0,1 +4,1,1,1,Tit For Tat,Tit For Tat,CCCCC,15,0,5,3.0,0.0,0,True,5,5,0,0,0,4,0,0,0,0,0,0,0,1 +4,1,1,1,Tit For Tat,Tit For Tat,CCCCC,15,0,5,3.0,0.0,0,True,5,5,0,0,0,4,0,0,0,0,0,0,0,1 +5,1,1,2,Tit For Tat,Tit For Tat,CCCCC,15,0,5,3.0,0.0,0,True,5,5,0,0,0,4,0,0,0,0,0,0,0,1 +5,1,1,2,Tit For Tat,Tit For Tat,CCCCC,15,0,5,3.0,0.0,0,True,5,5,0,0,0,4,0,0,0,0,0,0,0,1 +6,2,2,0,Defector,Defector,DDDDD,5,0,5,1.0,0.0,0,False,0,0,0,0,5,0,0,0,0,0,0,0,4,1 +6,2,2,0,Defector,Defector,DDDDD,5,0,5,1.0,0.0,0,False,0,0,0,0,5,0,0,0,0,0,0,0,4,1 +7,2,2,1,Defector,Defector,DDDDD,5,0,5,1.0,0.0,0,False,0,0,0,0,5,0,0,0,0,0,0,0,4,1 +7,2,2,1,Defector,Defector,DDDDD,5,0,5,1.0,0.0,0,False,0,0,0,0,5,0,0,0,0,0,0,0,4,1 +8,2,2,2,Defector,Defector,DDDDD,5,0,5,1.0,0.0,0,False,0,0,0,0,5,0,0,0,0,0,0,0,4,1 +8,2,2,2,Defector,Defector,DDDDD,5,0,5,1.0,0.0,0,False,0,0,0,0,5,0,0,0,0,0,0,0,4,1 +9,3,3,0,Cooperator,Cooperator,CCCCC,15,0,5,3.0,0.0,0,True,5,5,0,0,0,4,0,0,0,0,0,0,0,1 +9,3,3,0,Cooperator,Cooperator,CCCCC,15,0,5,3.0,0.0,0,True,5,5,0,0,0,4,0,0,0,0,0,0,0,1 +10,3,3,1,Cooperator,Cooperator,CCCCC,15,0,5,3.0,0.0,0,True,5,5,0,0,0,4,0,0,0,0,0,0,0,1 +10,3,3,1,Cooperator,Cooperator,CCCCC,15,0,5,3.0,0.0,0,True,5,5,0,0,0,4,0,0,0,0,0,0,0,1 +11,3,3,2,Cooperator,Cooperator,CCCCC,15,0,5,3.0,0.0,0,True,5,5,0,0,0,4,0,0,0,0,0,0,0,1 +11,3,3,2,Cooperator,Cooperator,CCCCC,15,0,5,3.0,0.0,0,True,5,5,0,0,0,4,0,0,0,0,0,0,0,1 diff --git a/test_outputs/test_results_spatial_two.csv b/test_outputs/test_results_spatial_two.csv new file mode 100644 index 000000000..e2a1cdfce --- /dev/null +++ b/test_outputs/test_results_spatial_two.csv @@ -0,0 +1,13 @@ +Interaction index,Player index,Opponent index,Repetition,Player name,Opponent name,Actions,Score,Score difference,Turns,Score per turn,Score difference per turn,Win,Initial cooperation,Cooperation count,CC count,CD count,DC count,DD count,CC to C count,CC to D count,CD to C count,CD to D count,DC to C count,DC to D count,DD to C count,DD to D count,Good partner +0,0,1,0,Alternator,Tit For Tat,CDCDC,13,0,5,2.6,0.0,0,True,3,1,2,2,0,0,1,0,1,2,0,0,0,1 +0,1,0,0,Tit For Tat,Alternator,CCDCD,13,0,5,2.6,0.0,0,True,3,1,2,2,0,1,0,0,2,1,0,0,0,1 +1,0,1,1,Alternator,Tit For Tat,CDCDC,13,0,5,2.6,0.0,0,True,3,1,2,2,0,0,1,0,1,2,0,0,0,1 +1,1,0,1,Tit For Tat,Alternator,CCDCD,13,0,5,2.6,0.0,0,True,3,1,2,2,0,1,0,0,2,1,0,0,0,1 +2,0,1,2,Alternator,Tit For Tat,CDCDC,13,0,5,2.6,0.0,0,True,3,1,2,2,0,0,1,0,1,2,0,0,0,1 +2,1,0,2,Tit For Tat,Alternator,CCDCD,13,0,5,2.6,0.0,0,True,3,1,2,2,0,1,0,0,2,1,0,0,0,1 +3,2,3,0,Defector,Cooperator,DDDDD,25,25,5,5.0,5.0,1,False,0,0,0,5,0,0,0,0,0,0,4,0,0,0 +3,3,2,0,Cooperator,Defector,CCCCC,0,-25,5,0.0,-5.0,0,True,5,0,5,0,0,0,0,4,0,0,0,0,0,1 +4,2,3,1,Defector,Cooperator,DDDDD,25,25,5,5.0,5.0,1,False,0,0,0,5,0,0,0,0,0,0,4,0,0,0 +4,3,2,1,Cooperator,Defector,CCCCC,0,-25,5,0.0,-5.0,0,True,5,0,5,0,0,0,0,4,0,0,0,0,0,1 +5,2,3,2,Defector,Cooperator,DDDDD,25,25,5,5.0,5.0,1,False,0,0,0,5,0,0,0,0,0,0,4,0,0,0 +5,3,2,2,Cooperator,Defector,CCCCC,0,-25,5,0.0,-5.0,0,True,5,0,5,0,0,0,0,4,0,0,0,0,0,1 From 9c9f3354333db348a74c5f9d7a953d317f668703 Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Mon, 29 Jan 2018 18:29:57 +0000 Subject: [PATCH 02/11] Add missing requirements and mocks for sphinx --- docs/conf.py | 4 +++- requirements.txt | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 3c79c2012..07a014086 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -21,7 +21,9 @@ 'scipy', 'scipy.stats','numpy', 'numpy.linalg', 'numpy.random', 'matplotlib.pyplot', 'matplotlib','matplotlib.transforms', 'tqdm', 'mpl_toolkits.axes_grid1', 'dill', 'multiprocess','prompt_toolkit', - 'prompt_toolkit.token', 'prompt_toolkit.styles','prompt_toolkit.validation'] + 'prompt_toolkit.token', 'prompt_toolkit.styles','prompt_toolkit.validation', + 'pandas', 'pandas.util', 'pandas.util.decorators', 'toolz', 'toolz.curried', + 'toolz.functoolz', 'cloudpickle', 'dask', 'dask.dataframe'] for mod_name in MOCK_MODULES: sys.modules[mod_name] = mock.Mock() diff --git a/requirements.txt b/requirements.txt index bb7e66cb9..b3ac362f6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,5 @@ scipy>=0.19.0 hypothesis==3.2 dask>=0.11.0 pandas>=0.18.1 +toolz>=0.8.0 +cloudpickle>=0.2.1 From 1cbd71a22730513fea31807407d0e8e7b0b2076f Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Tue, 30 Jan 2018 07:36:42 +0000 Subject: [PATCH 03/11] Add iu.read_interactions_from_file to docs. This highlights it as a user facing function but I'm happy to do something further. --- axelrod/interaction_utils.py | 4 +- .../reading_and_writing_interactions.rst | 177 +++++++++--------- 2 files changed, 85 insertions(+), 96 deletions(-) diff --git a/axelrod/interaction_utils.py b/axelrod/interaction_utils.py index 340058692..a453462f8 100644 --- a/axelrod/interaction_utils.py +++ b/axelrod/interaction_utils.py @@ -240,9 +240,7 @@ def compute_sparklines(interactions, c_symbol='█', d_symbol=' '): sparkline(histories[1], c_symbol, d_symbol)) -def read_interactions_from_file(filename, - progress_bar=True, - ): +def read_interactions_from_file(filename, progress_bar=True): """ Reads a file and returns a dictionary mapping tuples of player pairs to lists of interactions diff --git a/docs/tutorials/advanced/reading_and_writing_interactions.rst b/docs/tutorials/advanced/reading_and_writing_interactions.rst index 7552fea7f..f4a0de4f8 100644 --- a/docs/tutorials/advanced/reading_and_writing_interactions.rst +++ b/docs/tutorials/advanced/reading_and_writing_interactions.rst @@ -13,107 +13,98 @@ a :code:`filename` argument to the :code:`play` method of a tournament:: This will create a file `basic_tournament.csv` with data that looks something like:: - 0,0,Alternator,Alternator,CDCD,CDCD - 0,0,Alternator,Alternator,CDCD,CDCD - 0,1,Alternator,Anti Tit For Tat,CDCD,CDCD - 0,1,Alternator,Anti Tit For Tat,CDCD,CDCD - 0,2,Alternator,Bully,CDCD,DDCD - 0,2,Alternator,Bully,CDCD,DDCD - 0,3,Alternator,Cooperator,CDCD,CCCC - 0,3,Alternator,Cooperator,CDCD,CCCC - 0,4,Alternator,Defector,CDCD,DDDD - 0,4,Alternator,Defector,CDCD,DDDD - 0,5,Alternator,Suspicious Tit For Tat,CDCD,DCDC - 0,5,Alternator,Suspicious Tit For Tat,CDCD,DCDC - 0,6,Alternator,Tit For Tat,CDCD,CCDC - 0,6,Alternator,Tit For Tat,CDCD,CCDC - 0,7,Alternator,Win-Stay Lose-Shift,CDCD,CCDD - 0,7,Alternator,Win-Stay Lose-Shift,CDCD,CCDD - 1,1,Anti Tit For Tat,Anti Tit For Tat,CDCD,CDCD - 1,1,Anti Tit For Tat,Anti Tit For Tat,CDCD,CDCD - 1,2,Anti Tit For Tat,Bully,CCCC,DDDD - 1,2,Anti Tit For Tat,Bully,CCCC,DDDD - 1,3,Anti Tit For Tat,Cooperator,CDDD,CCCC - 1,3,Anti Tit For Tat,Cooperator,CDDD,CCCC - 1,4,Anti Tit For Tat,Defector,CCCC,DDDD - 1,4,Anti Tit For Tat,Defector,CCCC,DDDD - 1,5,Anti Tit For Tat,Suspicious Tit For Tat,CCDD,DCCD - 1,5,Anti Tit For Tat,Suspicious Tit For Tat,CCDD,DCCD - 1,6,Anti Tit For Tat,Tit For Tat,CDDC,CCDD - 1,6,Anti Tit For Tat,Tit For Tat,CDDC,CCDD - 1,7,Anti Tit For Tat,Win-Stay Lose-Shift,CDDC,CCDC - 1,7,Anti Tit For Tat,Win-Stay Lose-Shift,CDDC,CCDC - 2,2,Bully,Bully,DCDC,DCDC - 2,2,Bully,Bully,DCDC,DCDC - 2,3,Bully,Cooperator,DDDD,CCCC - 2,3,Bully,Cooperator,DDDD,CCCC - 2,4,Bully,Defector,DCCC,DDDD - 2,4,Bully,Defector,DCCC,DDDD - 2,5,Bully,Suspicious Tit For Tat,DCCD,DDCC - 2,5,Bully,Suspicious Tit For Tat,DCCD,DDCC - 2,6,Bully,Tit For Tat,DDCC,CDDC - 2,6,Bully,Tit For Tat,DDCC,CDDC - 2,7,Bully,Win-Stay Lose-Shift,DDCD,CDCC - 2,7,Bully,Win-Stay Lose-Shift,DDCD,CDCC - 3,3,Cooperator,Cooperator,CCCC,CCCC - 3,3,Cooperator,Cooperator,CCCC,CCCC - 3,4,Cooperator,Defector,CCCC,DDDD - 3,4,Cooperator,Defector,CCCC,DDDD - 3,5,Cooperator,Suspicious Tit For Tat,CCCC,DCCC - 3,5,Cooperator,Suspicious Tit For Tat,CCCC,DCCC - 3,6,Cooperator,Tit For Tat,CCCC,CCCC - 3,6,Cooperator,Tit For Tat,CCCC,CCCC - 3,7,Cooperator,Win-Stay Lose-Shift,CCCC,CCCC - 3,7,Cooperator,Win-Stay Lose-Shift,CCCC,CCCC - 4,4,Defector,Defector,DDDD,DDDD - 4,4,Defector,Defector,DDDD,DDDD - 4,5,Defector,Suspicious Tit For Tat,DDDD,DDDD - 4,5,Defector,Suspicious Tit For Tat,DDDD,DDDD - 4,6,Defector,Tit For Tat,DDDD,CDDD - 4,6,Defector,Tit For Tat,DDDD,CDDD - 4,7,Defector,Win-Stay Lose-Shift,DDDD,CDCD - 4,7,Defector,Win-Stay Lose-Shift,DDDD,CDCD - 5,5,Suspicious Tit For Tat,Suspicious Tit For Tat,DDDD,DDDD - 5,5,Suspicious Tit For Tat,Suspicious Tit For Tat,DDDD,DDDD - 5,6,Suspicious Tit For Tat,Tit For Tat,DCDC,CDCD - 5,6,Suspicious Tit For Tat,Tit For Tat,DCDC,CDCD - 5,7,Suspicious Tit For Tat,Win-Stay Lose-Shift,DCDD,CDDC - 5,7,Suspicious Tit For Tat,Win-Stay Lose-Shift,DCDD,CDDC - 6,6,Tit For Tat,Tit For Tat,CCCC,CCCC - 6,6,Tit For Tat,Tit For Tat,CCCC,CCCC - 6,7,Tit For Tat,Win-Stay Lose-Shift,CCCC,CCCC - 6,7,Tit For Tat,Win-Stay Lose-Shift,CCCC,CCCC - 7,7,Win-Stay Lose-Shift,Win-Stay Lose-Shift,CCCC,CCCC - 7,7,Win-Stay Lose-Shift,Win-Stay Lose-Shift,CCCC,CCCC - -The columns of this file are of the form: - -1. Index of first player -2. Index of second player -3. Name of first player -4. Name of second player -5. History of play of the first player -6. History of play of the second player + Interaction index,Player index,Opponent index,Repetition,Player name,Opponent name,Actions,Score,Score difference,Turns,Score per turn,Score difference per turn,Win,Initial cooperation,Cooperation count,CC count,CD count,DC count,DD count,CC to C count,CC to D count,CD to C count,CD to D count,DC to C count,DC to D count,DD to C count,DD to D count,Good partner + 0,0,0,0,Alternator,Alternator,CDCD,8,0,4,2.0,0.0,0,True,2,2,0,0,2,0,2,0,0,0,0,1,0,1 + 0,0,0,0,Alternator,Alternator,CDCD,8,0,4,2.0,0.0,0,True,2,2,0,0,2,0,2,0,0,0,0,1,0,1 + 1,0,0,1,Alternator,Alternator,CDCD,8,0,4,2.0,0.0,0,True,2,2,0,0,2,0,2,0,0,0,0,1,0,1 + 1,0,0,1,Alternator,Alternator,CDCD,8,0,4,2.0,0.0,0,True,2,2,0,0,2,0,2,0,0,0,0,1,0,1 + 2,0,1,0,Alternator,Anti Tit For Tat,CDCD,8,0,4,2.0,0.0,0,True,2,2,0,0,2,0,2,0,0,0,0,1,0,1 + 2,1,0,0,Anti Tit For Tat,Alternator,CDCD,8,0,4,2.0,0.0,0,True,2,2,0,0,2,0,2,0,0,0,0,1,0,1 + 3,0,1,1,Alternator,Anti Tit For Tat,CDCD,8,0,4,2.0,0.0,0,True,2,2,0,0,2,0,2,0,0,0,0,1,0,1 + 3,1,0,1,Anti Tit For Tat,Alternator,CDCD,8,0,4,2.0,0.0,0,True,2,2,0,0,2,0,2,0,0,0,0,1,0,1 + 4,0,2,0,Alternator,Bully,CDCD,5,-5,4,1.25,-1.25,0,True,2,1,1,0,2,0,1,0,1,0,0,1,0,1 + 4,2,0,0,Bully,Alternator,DDCD,10,5,4,2.5,1.25,1,False,1,1,0,1,2,0,1,0,0,0,1,1,0,0 + 5,0,2,1,Alternator,Bully,CDCD,5,-5,4,1.25,-1.25,0,True,2,1,1,0,2,0,1,0,1,0,0,1,0,1 + 5,2,0,1,Bully,Alternator,DDCD,10,5,4,2.5,1.25,1,False,1,1,0,1,2,0,1,0,0,0,1,1,0,0 + 6,0,3,0,Alternator,Cooperator,CDCD,16,10,4,4.0,2.5,1,True,2,2,0,2,0,0,2,0,0,1,0,0,0,0 + 6,3,0,0,Cooperator,Alternator,CCCC,6,-10,4,1.5,-2.5,0,True,4,2,2,0,0,2,0,1,0,0,0,0,0,1 + 7,0,3,1,Alternator,Cooperator,CDCD,16,10,4,4.0,2.5,1,True,2,2,0,2,0,0,2,0,0,1,0,0,0,0 + 7,3,0,1,Cooperator,Alternator,CCCC,6,-10,4,1.5,-2.5,0,True,4,2,2,0,0,2,0,1,0,0,0,0,0,1 + 8,0,4,0,Alternator,Cycler DC,CDCD,10,0,4,2.5,0.0,0,True,2,0,2,2,0,0,0,0,2,1,0,0,0,1 + 8,4,0,0,Cycler DC,Alternator,DCDC,10,0,4,2.5,0.0,0,False,2,0,2,2,0,0,0,0,1,2,0,0,0,1 + 9,0,4,1,Alternator,Cycler DC,CDCD,10,0,4,2.5,0.0,0,True,2,0,2,2,0,0,0,0,2,1,0,0,0,1 + 9,4,0,1,Cycler DC,Alternator,DCDC,10,0,4,2.5,0.0,0,False,2,0,2,2,0,0,0,0,1,2,0,0,0,1 + 10,0,5,0,Alternator,Defector,CDCD,2,-10,4,0.5,-2.5,0,True,2,0,2,0,2,0,0,0,2,0,0,1,0,1 + 10,5,0,0,Defector,Alternator,DDDD,12,10,4,3.0,2.5,1,False,0,0,0,2,2,0,0,0,0,0,2,0,1,0 + 11,0,5,1,Alternator,Defector,CDCD,2,-10,4,0.5,-2.5,0,True,2,0,2,0,2,0,0,0,2,0,0,1,0,1 + 11,5,0,1,Defector,Alternator,DDDD,12,10,4,3.0,2.5,1,False,0,0,0,2,2,0,0,0,0,0,2,0,1,0 + 12,0,6,0,Alternator,Grudger,CDCD,9,0,4,2.25,0.0,0,True,2,1,1,1,1,0,1,0,1,1,0,0,0,1 + 12,6,0,0,Grudger,Alternator,CCDD,9,0,4,2.25,0.0,0,True,2,1,1,1,1,1,0,0,1,0,1,0,0,1 + 13,0,6,1,Alternator,Grudger,CDCD,9,0,4,2.25,0.0,0,True,2,1,1,1,1,0,1,0,1,1,0,0,0,1 + 13,6,0,1,Grudger,Alternator,CCDD,9,0,4,2.25,0.0,0,True,2,1,1,1,1,1,0,0,1,0,1,0,0,1 + 14,0,7,0,Alternator,Suspicious Tit For Tat,CDCD,10,0,4,2.5,0.0,0,True,2,0,2,2,0,0,0,0,2,1,0,0,0,1 + 14,7,0,0,Suspicious Tit For Tat,Alternator,DCDC,10,0,4,2.5,0.0,0,False,2,0,2,2,0,0,0,0,1,2,0,0,0,1 + 15,0,7,1,Alternator,Suspicious Tit For Tat,CDCD,10,0,4,2.5,0.0,0,True,2,0,2,2,0,0,0,0,2,1,0,0,0,1 + 15,7,0,1,Suspicious Tit For Tat,Alternator,DCDC,10,0,4,2.5,0.0,0,False,2,0,2,2,0,0,0,0,1,2,0,0,0,1 + 16,0,8,0,Alternator,Tit For Tat,CDCD,13,5,4,3.25,1.25,1,True,2,1,1,2,0,0,1,0,1,1,0,0,0,0 + 16,8,0,0,Tit For Tat,Alternator,CCDC,8,-5,4,2.0,-1.25,0,True,3,1,2,1,0,1,0,0,1,1,0,0,0,1 + 17,0,8,1,Alternator,Tit For Tat,CDCD,13,5,4,3.25,1.25,1,True,2,1,1,2,0,0,1,0,1,1,0,0,0,0 + 17,8,0,1,Tit For Tat,Alternator,CCDC,8,-5,4,2.0,-1.25,0,True,3,1,2,1,0,1,0,0,1,1,0,0,0,1 + 18,0,9,0,Alternator,Win-Shift Lose-Stay: D,CDCD,9,0,4,2.25,0.0,0,True,2,1,1,1,1,0,1,0,1,1,0,0,0,1 + 18,9,0,0,Win-Shift Lose-Stay: D,Alternator,DCCD,9,0,4,2.25,0.0,0,False,2,1,1,1,1,0,1,1,0,1,0,0,0,1 + 19,0,9,1,Alternator,Win-Shift Lose-Stay: D,CDCD,9,0,4,2.25,0.0,0,True,2,1,1,1,1,0,1,0,1,1,0,0,0,1 + 19,9,0,1,Win-Shift Lose-Stay: D,Alternator,DCCD,9,0,4,2.25,0.0,0,False,2,1,1,1,1,0,1,1,0,1,0,0,0,1 + 20,0,10,0,Alternator,Win-Stay Lose-Shift: C,CDCD,9,0,4,2.25,0.0,0,True,2,1,1,1,1,0,1,0,1,1,0,0,0,1 + 20,10,0,0,Win-Stay Lose-Shift: C,Alternator,CCDD,9,0,4,2.25,0.0,0,True,2,1,1,1,1,1,0,0,1,0,1,0,0,1 + 21,0,10,1,Alternator,Win-Stay Lose-Shift: C,CDCD,9,0,4,2.25,0.0,0,True,2,1,1,1,1,0,1,0,1,1,0,0,0,1 + 21,10,0,1,Win-Stay Lose-Shift: C,Alternator,CCDD,9,0,4,2.25,0.0,0,True,2,1,1,1,1,1,0,0,1,0,1,0,0,1 + 22,1,1,0,Anti Tit For Tat,Anti Tit For Tat,CDCD,8,0,4,2.0,0.0,0,True,2,2,0,0,2,0,2,0,0,0,0,1,0,1 + 22,1,1,0,Anti Tit For Tat,Anti Tit For Tat,CDCD,8,0,4,2.0,0.0,0,True,2,2,0,0,2,0,2,0,0,0,0,1,0,1 + 23,1,1,1,Anti Tit For Tat,Anti Tit For Tat,CDCD,8,0,4,2.0,0.0,0,True,2,2,0,0,2,0,2,0,0,0,0,1,0,1 + 23,1,1,1,Anti Tit For Tat,Anti Tit For Tat,CDCD,8,0,4,2.0,0.0,0,True,2,2,0,0,2,0,2,0,0,0,0,1,0,1 + 24,1,2,0,Anti Tit For Tat,Bully,CCCC,0,-20,4,0.0,-5.0,0,True,4,0,4,0,0,0,0,3,0,0,0,0,0,1 + 24,2,1,0,Bully,Anti Tit For Tat,DDDD,20,20,4,5.0,5.0,1,False,0,0,0,4,0,0,0,0,0,0,3,0,0,0 + 25,1,2,1,Anti Tit For Tat,Bully,CCCC,0,-20,4,0.0,-5.0,0,True,4,0,4,0,0,0,0,3,0,0,0,0,0,1 + 25,2,1,1,Bully,Anti Tit For Tat,DDDD,20,20,4,5.0,5.0,1,False,0,0,0,4,0,0,0,0,0,0,3,0,0,0 + 26,1,3,0,Anti Tit For Tat,Cooperator,CDDD,18,15,4,4.5,3.75,1,True,1,1,0,3,0,0,1,0,0,0,2,0,0,0 + 26,3,1,0,Cooperator,Anti Tit For Tat,CCCC,3,-15,4,0.75,-3.75,0,True,4,1,3,0,0,1,0,2,0,0,0,0,0,1 + 27,1,3,1,Anti Tit For Tat,Cooperator,CDDD,18,15,4,4.5,3.75,1,True,1,1,0,3,0,0,1,0,0,0,2,0,0,0 + 27,3,1,1,Cooperator,Anti Tit For Tat,CCCC,3,-15,4,0.75,-3.75,0,True,4,1,3,0,0,1,0,2,0,0,0,0,0,1 + 28,1,4,0,Anti Tit For Tat,Cycler DC,CCDC,7,-5,4,1.75,-1.25,0,True,3,2,1,0,1,0,1,1,0,0,0,1,0,1 + 28,4,1,0,Cycler DC,Anti Tit For Tat,DCDC,12,5,4,3.0,1.25,1,False,2,2,0,1,1,0,1,0,0,1,0,1,0,0 + 29,1,4,1,Anti Tit For Tat,Cycler DC,CCDC,7,-5,4,1.75,-1.25,0,True,3,2,1,0,1,0,1,1,0,0,0,1,0,1 + 29,4,1,1,Cycler DC,Anti Tit For Tat,DCDC,12,5,4,3.0,1.25,1,False,2,2,0,1,1,0,1,0,0,1,0,1,0,0 + 30,1,5,0,Anti Tit For Tat,Defector,CCCC,0,-20,4,0.0,-5.0,0,True,4,0,4,0,0,0,0,3,0,0,0,0,0,1 + 30,5,1,0,Defector,Anti Tit For Tat,DDDD,20,20,4,5.0,5.0,1,False,0,0,0,4,0,0,0,0,0,0,3,0,0,0 + 31,1,5,1,Anti Tit For Tat,Defector,CCCC,0,-20,4,0.0,-5.0,0,True,4,0,4,0,0,0,0,3,0,0,0,0,0,1 + 31,5,1,1,Defector,Anti Tit For Tat,DDDD,20,20,4,5.0,5.0,1,False,0,0,0,4,0,0,0,0,0,0,3,0,0,0 + 32,1,6,0,Anti Tit For Tat,Grudger,CDDC,9,0,4,2.25,0.0,0,True,2,1,1,1,1,0,1,0,0,0,1,1,0,1 + 32,6,1,0,Grudger,Anti Tit For Tat,CCDD,9,0,4,2.25,0.0,0,True,2,1,1,1,1,1,0,0,1,0,0,0,1,1 + 33,1,6,1,Anti Tit For Tat,Grudger,CDDC,9,0,4,2.25,0.0,0,True,2,1,1,1,1,0,1,0,0,0,1,1,0,1 + 33,6,1,1,Grudger,Anti Tit For Tat,CCDD,9,0,4,2.25,0.0,0,True,2,1,1,1,1,1,0,0,1,0,0,0,1,1 + 34,1,7,0,Anti Tit For Tat,Suspicious Tit For Tat,CCDD,9,0,4,2.25,0.0,0,True,2,1,1,1,1,0,1,1,0,0,1,0,0,1 + 34,7,1,0,Suspicious Tit For Tat,Anti Tit For Tat,DCCD,9,0,4,2.25,0.0,0,False,2,1,1,1,1,1,0,0,1,1,0,0,0,1 + 35,1,7,1,Anti Tit For Tat,Suspicious Tit For Tat,CCDD,9,0,4,2.25,0.0,0,True,2,1,1,1,1,0,1,1,0,0,1,0,0,1 + 35,7,1,1,Suspicious Tit For Tat,Anti Tit For Tat,DCCD,9,0,4,2.25,0.0,0,False,2,1,1,1,1,1,0,0,1,1,0,0,0,1 + ... Note that depending on the order in which the matches have been played, the rows could also be in a different order. -:code:`Alternator` versus :code:`TitForTat` has the following interactions: -:code:`CCDC, CDCD`: +It is possible to read in this data file to obtain interactions:: -- First turn: :code:`C` versus :code:`C` (the first two letters) -- Second turn: :code:`D` versus :code:`C` (the second pair of letters) -- Third turn: :code:`C` versus :code:`D` (the third pair of letters) -- Fourth turn: :code:`D` versus :code:`C` (the fourth pair of letters) + >>> interactions = axl.interaction_utils.read_interactions_from_file("basic_tournament.csv") -This can be transformed in to the usual interactions by zipping: +This gives a dictionary mapping pairs of player indices to interaction +histories:: - >>> from axelrod.action import str_to_actions - >>> list(zip(str_to_actions("CCDC"), str_to_actions("CDCD"))) - [(C, C), (C, D), (D, C), (C, D)] + >>> interactions[(0, 1)] + [[(C, C), (D, D), (C, C), (D, D)], [(C, C), (D, D), (C, C), (D, D)]] This should allow for easy manipulation of data outside of the capabilities -within the library. Note that you can supply `build_results=False` as a keyword +within the library. + +Note that you can supply `build_results=False` as a keyword argument to `tournament.play()` to prevent keeping or loading interactions in memory, since the total memory footprint can be large for various combinations -of parameters. The memory usage scales as :math:`O(\text{players}^2 * \text{turns} * \text{repetitions})`. +of parameters. The memory usage scales as :math:`O(\text{players}^2 \times \text{turns} \times \text{repetitions})`. From da99c799ae3c56b929b02eb682cee54fff786e6a Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Tue, 30 Jan 2018 07:41:43 +0000 Subject: [PATCH 04/11] Use defaultdict. --- axelrod/interaction_utils.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/axelrod/interaction_utils.py b/axelrod/interaction_utils.py index a453462f8..7488e9ca4 100644 --- a/axelrod/interaction_utils.py +++ b/axelrod/interaction_utils.py @@ -7,7 +7,7 @@ This is used by both the Match class and the ResultSet class which analyse interactions. """ -from collections import Counter +from collections import Counter, defaultdict import csv import tqdm import pandas as pd @@ -251,14 +251,11 @@ def read_interactions_from_file(filename, progress_bar=True): if progress_bar: groupby = tqdm.tqdm(groupby) - pairs_to_interactions = {} + pairs_to_interactions = defaultdict(list) for _, d in tqdm.tqdm(groupby): key = tuple(d[["Player index", "Opponent index"]].iloc[0]) value = list(map(str_to_actions, zip(*d["Actions"]))) - try: - pairs_to_interactions[key].append(value) - except KeyError: - pairs_to_interactions[key] = [value] + pairs_to_interactions[key].append(value) return pairs_to_interactions From f8b53aac8300b2f666af81b0dda6fae2df42efc1 Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Tue, 30 Jan 2018 07:45:00 +0000 Subject: [PATCH 05/11] Modify file comparison to not be shallow. Hopefully this now works on windows. --- axelrod/tests/unit/test_tournament.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/axelrod/tests/unit/test_tournament.py b/axelrod/tests/unit/test_tournament.py index af1dea525..0859f9b5d 100644 --- a/axelrod/tests/unit/test_tournament.py +++ b/axelrod/tests/unit/test_tournament.py @@ -686,7 +686,8 @@ def test_write_to_csv_with_results(self): repetitions=2) tournament.play(filename=self.filename, progress_bar=False) self.assertTrue(filecmp.cmp(self.filename, - "test_outputs/expected_test_tournament.csv")) + "test_outputs/expected_test_tournament.csv", + shallow=False)) def test_write_to_csv_without_results(self): tournament = axelrod.Tournament( @@ -698,7 +699,8 @@ def test_write_to_csv_without_results(self): tournament.play(filename=self.filename, progress_bar=False, build_results=False) self.assertTrue(filecmp.cmp(self.filename, - "test_outputs/expected_test_tournament_no_results.csv")) + "test_outputs/expected_test_tournament_no_results.csv", + shallow=False)) class TestProbEndTournament(unittest.TestCase): From b0eb05d91822baf57d814a91507e2d2d6bb48278 Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Tue, 30 Jan 2018 08:37:21 +0000 Subject: [PATCH 06/11] Use pd.Dataframe.equals Attempting to make file comparison work on windows. --- axelrod/tests/unit/test_tournament.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/axelrod/tests/unit/test_tournament.py b/axelrod/tests/unit/test_tournament.py index 0859f9b5d..d4eac0107 100644 --- a/axelrod/tests/unit/test_tournament.py +++ b/axelrod/tests/unit/test_tournament.py @@ -15,6 +15,7 @@ from hypothesis.strategies import integers, floats from tqdm import tqdm import numpy as np +import pandas as pd from axelrod.tests.property import (tournaments, prob_end_tournaments, @@ -685,9 +686,9 @@ def test_write_to_csv_with_results(self): turns=2, repetitions=2) tournament.play(filename=self.filename, progress_bar=False) - self.assertTrue(filecmp.cmp(self.filename, - "test_outputs/expected_test_tournament.csv", - shallow=False)) + df = pd.read_csv(self.filename) + expected_df = pd.read_csv("test_outputs/expected_test_tournament.csv") + self.assertTrue(df.equals(expected_df)) def test_write_to_csv_without_results(self): tournament = axelrod.Tournament( @@ -698,9 +699,9 @@ def test_write_to_csv_without_results(self): repetitions=2) tournament.play(filename=self.filename, progress_bar=False, build_results=False) - self.assertTrue(filecmp.cmp(self.filename, - "test_outputs/expected_test_tournament_no_results.csv", - shallow=False)) + df = pd.read_csv(self.filename) + expected_df = pd.read_csv("test_outputs/expected_test_tournament_no_results.csv") + self.assertTrue(df.equals(expected_df)) class TestProbEndTournament(unittest.TestCase): From e3da817b2c58c48ee60448b04de63dac339a0911 Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Thu, 1 Feb 2018 08:52:29 +0000 Subject: [PATCH 07/11] Move swapping of states order to outer context Note I did not use `reversed()`, this doesn't return a tuple, I also didn't use `.reverse()` this only works on lists. --- axelrod/tournament.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/axelrod/tournament.py b/axelrod/tournament.py index 5e51a6e2e..a5a7f254a 100644 --- a/axelrod/tournament.py +++ b/axelrod/tournament.py @@ -252,13 +252,11 @@ def _write_interactions_to_file(self, results, writer): row.append(cooperations[index]) states = [(C, C), (C, D), (D, C), (D, D)] + if index == 1: + states = [s[::-1] for s in states] for state in states: - if index == 1: - state = state[::-1] row.append(state_distribution[state]) for state in states: - if index == 1: - state = state[::-1] row.append(state_to_action_distributions[index][(state, C)]) row.append(state_to_action_distributions[index][(state, D)]) From e10fb4fc388ca35ae71437f1236482688949ed61 Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Thu, 1 Feb 2018 09:05:57 +0000 Subject: [PATCH 08/11] Fix spacing and minor refactor. --- axelrod/result_set.py | 40 +++++++++++++++++++++------------------- axelrod/tournament.py | 3 +-- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/axelrod/result_set.py b/axelrod/result_set.py index 810b02b32..877de1bbf 100644 --- a/axelrod/result_set.py +++ b/axelrod/result_set.py @@ -72,24 +72,23 @@ def __init__(self, filename, out = self._compute_tasks(tasks=dask_tasks, processes=processes) - self._reshape_out(out) + self._reshape_out(*out) if progress_bar: self.progress_bar.close() - def _reshape_out(self, out): + def _reshape_out(self, + mean_per_reps_player_opponent_df, + sum_per_player_opponent_df, + sum_per_player_repetition_df, + normalised_scores_series, + initial_cooperation_count_series, + interactions_count_series): """ Reshape the various pandas series objects to be of the required form and set the corresponding attributes. """ - (mean_per_reps_player_opponent_df, - sum_per_player_opponent_df, - sum_per_player_repetition_df, - normalised_scores_series, - initial_cooperation_count_series, - interactions_count_series) = out - self.payoffs = self._build_payoffs(mean_per_reps_player_opponent_df["Score per turn"]) self.score_diffs = self._build_score_diffs(mean_per_reps_player_opponent_df["Score difference per turn"]) self.match_lengths = self._build_match_lengths(mean_per_reps_player_opponent_df["Turns"]) @@ -269,8 +268,8 @@ def _build_good_partner_matrix(self, good_partner_series): # interactions. row.append(0) else: - row.append(good_partner_dict.get((player_index, - opponent_index), 0)) + row.append(good_partner_dict.get( + (player_index, opponent_index), 0)) good_partner_matrix.append(row) return good_partner_matrix @@ -398,10 +397,9 @@ def _build_normalised_state_to_action_distribution(self): @update_progress_bar def _build_initial_cooperation_count(self, initial_cooperation_count_series): initial_cooperation_count_dict = initial_cooperation_count_series.to_dict() - initial_cooperation_count = [ - initial_cooperation_count_dict.get(player_index, 0) - for player_index in - range(self.num_players)] + initial_cooperation_count = [initial_cooperation_count_dict.get(player_index, 0) + for player_index in + range(self.num_players)] return initial_cooperation_count @update_progress_bar @@ -564,10 +562,14 @@ def _build_tasks(self, df): columns = ["Win", "Score"] sum_per_player_repetition_task = adf.groupby(groups)[columns].sum() - normalised_scores_task = adf.groupby(["Player index", - "Repetition"] - )["Score per turn"].mean() - initial_cooperation_count_task = adf.groupby(["Player index"])["Initial cooperation"].sum() + groups = ["Player index", "Repetition"] + column = "Score per turn" + normalised_scores_task = adf.groupby(groups)[column].mean() + + + groups = ["Player index"] + column = "Initial cooperation" + initial_cooperation_count_task = adf.groupby(groups)[column].sum() interactions_count_task = adf.groupby("Player index")["Player index"].count() diff --git a/axelrod/tournament.py b/axelrod/tournament.py index a5a7f254a..bd3c3ef5e 100644 --- a/axelrod/tournament.py +++ b/axelrod/tournament.py @@ -136,8 +136,7 @@ def play(self, build_results: bool = True, filename: str = None, result_set = None if build_results: - result_set = ResultSet( - filename=self.filename, + result_set = ResultSet(filename=self.filename, players=[str(p) for p in self.players], repetitions=self.repetitions, processes=processes, From 30dfed13b4de07c5b4471d38cb8e949102a4e377 Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Thu, 1 Feb 2018 09:10:57 +0000 Subject: [PATCH 09/11] Add a comment explaining the filtering. --- axelrod/fingerprint.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/axelrod/fingerprint.py b/axelrod/fingerprint.py index b3518f956..1b1a289a1 100644 --- a/axelrod/fingerprint.py +++ b/axelrod/fingerprint.py @@ -484,6 +484,8 @@ def analyse_cooperation_ratio(filename): cooperation_rates = {} df = dd.read_csv(filename) + # We ignore the actions of all opponents. So we filter the dataframe to + # only include the results of the player with index `0`. df = df[df["Player index"] == 0][["Opponent index", "Actions"]] for _, row in df.iterrows(): From 5470a4c75a894f736a575a5f79d9aefd3a8c010a Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Thu, 1 Feb 2018 09:18:19 +0000 Subject: [PATCH 10/11] Use np namespace. --- axelrod/result_set.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/axelrod/result_set.py b/axelrod/result_set.py index 877de1bbf..fef9a8412 100644 --- a/axelrod/result_set.py +++ b/axelrod/result_set.py @@ -3,7 +3,7 @@ import csv import itertools -from numpy import mean, nanmedian, std, array, nan_to_num +import numpy as np import tqdm import dask as da @@ -284,7 +284,7 @@ def _build_payoff_matrix(self): for player_index, opponent_index in pairs: utilities = self.payoffs[player_index][opponent_index] if utilities: - payoff_matrix[player_index][opponent_index] = mean(utilities) + payoff_matrix[player_index][opponent_index] = np.mean(utilities) return payoff_matrix @@ -298,14 +298,14 @@ def _build_payoff_stddevs(self): for player_index, opponent_index in pairs: utilities = self.payoffs[player_index][opponent_index] if utilities: - payoff_stddevs[player_index][opponent_index] = std(utilities) + payoff_stddevs[player_index][opponent_index] = np.std(utilities) return payoff_stddevs @update_progress_bar def _build_payoff_diffs_means(self): - payoff_diffs_means = [[mean(diff) for diff in player] + payoff_diffs_means = [[np.mean(diff) for diff in player] for player in self.score_diffs] return payoff_diffs_means @@ -404,26 +404,26 @@ def _build_initial_cooperation_count(self, initial_cooperation_count_series): @update_progress_bar def _build_normalised_cooperation(self): - normalised_cooperation = [list(nan_to_num(row)) - for row in array(self.cooperation) / - sum(map(array, self.match_lengths))] + normalised_cooperation = [list(np.nan_to_num(row)) + for row in np.array(self.cooperation) / + sum(map(np.array, self.match_lengths))] return normalised_cooperation @update_progress_bar def _build_initial_cooperation_rate(self, interactions_series): interactions_dict = interactions_series.to_dict() - interactions_array = array([interactions_series.get(player_index, 0) - for player_index in range(self.num_players)]) + interactions_array = np.array([interactions_series.get(player_index, 0) + for player_index in range(self.num_players)]) initial_cooperation_rate = list( - nan_to_num(array(self.initial_cooperation_count) / - interactions_array)) + np.nan_to_num(np.array(self.initial_cooperation_count) / + interactions_array)) return initial_cooperation_rate @update_progress_bar def _build_ranking(self): ranking = sorted( range(self.num_players), - key=lambda i: -nanmedian(self.normalised_scores[i])) + key=lambda i: -np.nanmedian(self.normalised_scores[i])) return ranking @update_progress_bar @@ -637,8 +637,8 @@ def summarise(self): """ - median_scores = map(nanmedian, self.normalised_scores) - median_wins = map(nanmedian, self.wins) + median_scores = map(np.nanmedian, self.normalised_scores) + median_wins = map(np.nanmedian, self.wins) self.player = namedtuple("Player", ["Rank", "Name", "Median_score", "Cooperation_rating", "Wins", @@ -668,7 +668,7 @@ def summarise(self): if counter[(state, C)] > 0] if len(counts) > 0: - rate = mean(counts) + rate = np.mean(counts) else: rate = 0 From 43cfc5316d9a7a06490b07dbd5a41de292d84aef Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Thu, 1 Feb 2018 10:46:32 +0000 Subject: [PATCH 11/11] Abstract various functions where possible. --- axelrod/result_set.py | 192 ++++++++++++++++-------------------------- 1 file changed, 73 insertions(+), 119 deletions(-) diff --git a/axelrod/result_set.py b/axelrod/result_set.py index fef9a8412..035c161aa 100644 --- a/axelrod/result_set.py +++ b/axelrod/result_set.py @@ -20,9 +20,9 @@ def update_progress_bar(method): """A decorator to update a progress bar if it exists""" - def wrapper(*args): + def wrapper(*args, **kwargs): """Run the method and update the progress bar if it exists""" - output = method(*args) + output = method(*args, **kwargs) try: args[0].progress_bar.update(1) @@ -89,13 +89,32 @@ def _reshape_out(self, set the corresponding attributes. """ - self.payoffs = self._build_payoffs(mean_per_reps_player_opponent_df["Score per turn"]) - self.score_diffs = self._build_score_diffs(mean_per_reps_player_opponent_df["Score difference per turn"]) - self.match_lengths = self._build_match_lengths(mean_per_reps_player_opponent_df["Turns"]) + self.payoffs = self._reshape_three_dim_list( + mean_per_reps_player_opponent_df["Score per turn"], + first_dimension=range(self.num_players), + second_dimension=range(self.num_players), + third_dimension=range(self.repetitions), + key_order=[2, 0, 1]) + + self.score_diffs = self._reshape_three_dim_list( + mean_per_reps_player_opponent_df["Score difference per turn"], + first_dimension=range(self.num_players), + second_dimension=range(self.num_players), + third_dimension=range(self.repetitions), + key_order=[2, 0, 1], + alternative=0) + + self.match_lengths = self._reshape_three_dim_list( + mean_per_reps_player_opponent_df["Turns"], + first_dimension=range(self.repetitions), + second_dimension=range(self.num_players), + third_dimension=range(self.num_players), + alternative=0) + + self.wins = self._reshape_two_dim_list(sum_per_player_repetition_df["Win"]) + self.scores = self._reshape_two_dim_list(sum_per_player_repetition_df["Score"]) + self.normalised_scores = self._reshape_two_dim_list(normalised_scores_series) - self.wins = self._build_wins(sum_per_player_repetition_df["Win"]) - self.scores = self._build_scores(sum_per_player_repetition_df["Score"]) - self.normalised_scores = self._build_normalised_scores(normalised_scores_series) self.cooperation = self._build_cooperation(sum_per_player_opponent_df["Cooperation count"]) self.good_partner_matrix = self._build_good_partner_matrix(sum_per_player_opponent_df["Good partner"]) @@ -121,8 +140,11 @@ def _reshape_out(self, self.normalised_cooperation = self._build_normalised_cooperation() self.ranking = self._build_ranking() self.ranked_names = self._build_ranked_names() - self.payoff_matrix = self._build_payoff_matrix() - self.payoff_stddevs = self._build_payoff_stddevs() + + self.payoff_matrix = self._build_summary_matrix(self.payoffs) + self.payoff_stddevs = self._build_summary_matrix(self.payoffs, + func=np.std) + self.payoff_diffs_means = self._build_payoff_diffs_means() self.cooperating_rating = self._build_cooperating_rating() self.vengeful_cooperation = self._build_vengeful_cooperation() @@ -130,116 +152,64 @@ def _reshape_out(self, self.eigenmoses_rating = self._build_eigenmoses_rating() @update_progress_bar - def _build_payoffs(self, payoffs_series): + def _reshape_three_dim_list(self, series, + first_dimension, + second_dimension, + third_dimension, + alternative=None, + key_order=[0, 1, 2]): """ Parameters ---------- payoffs_series : pandas.Series + first_dimension : iterable + second_dimension : iterable + third_dimension : iterable + alternative : int + What to do if there is no entry at given position + key_order : list + Indices re-ording the dimensions to the correct keys in the + series 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: - [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 mean utility (over - all repetitions) obtained by player i against player j. + A three dimensional list across the three dimensions """ - payoffs_dict = dict(payoffs_series) - payoffs = [] - for player_index in range(self.num_players): + series_dict = series.to_dict() + output = [] + for first_index in first_dimension: matrix = [] - for opponent_index in range(self.num_players): + for second_index in second_dimension: row = [] - for repetition in range(self.repetitions): - key = (repetition, player_index, opponent_index) - if key in payoffs_dict: - row.append(payoffs_dict[key]) + for third_index in third_dimension: + key = (first_index, second_index, third_index) + key = tuple([key[order] for order in key_order]) + if key in series_dict: + row.append(series_dict[key]) + elif alternative is not None: + row.append(alternative) matrix.append(row) - payoffs.append(matrix) - return payoffs + output.append(matrix) + return output @update_progress_bar - def _build_score_diffs(self, payoff_diffs_series): + def _reshape_two_dim_list(self, series): """ Parameters ---------- - payoffs_diffs_series : pandas.Series + series : pandas.Series Returns: -------- - The mean of per turn payoff differences - 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 u is the mean utility - difference (over all repetitions) obtained by player i against - player j. + A two dimensional list across repetitions and opponents """ - - payoff_diffs_dict = payoff_diffs_series.to_dict() - score_diffs = [] - for player_index in range(self.num_players): - matrix = [] - for opponent_index in range(self.num_players): - row = [] - for repetition in range(self.repetitions): - row.append(payoff_diffs_dict.get((repetition, - player_index, - opponent_index, - ), 0)) - matrix.append(row) - score_diffs.append(matrix) - return score_diffs - - @update_progress_bar - def _build_match_lengths(self, length_series): - length_dict = dict(length_series) - match_lengths = [] - for repetition in range(self.repetitions): - matrix = [] - for player_index in range(self.num_players): - row = [] - for opponent_index in range(self.num_players): - row.append(length_dict.get((repetition, - player_index, - opponent_index), 0)) - matrix.append(row) - match_lengths.append(matrix) - return match_lengths - - @update_progress_bar - def _build_wins(self, wins_series): - wins_dict = wins_series.to_dict() - wins = [[wins_dict.get((player_index, repetition), 0) + series_dict = series.to_dict() + out = [[series_dict.get((player_index, repetition), 0) for repetition in range(self.repetitions)] for player_index in range(self.num_players)] - return wins - - @update_progress_bar - def _build_scores(self, scores_series): - scores_dict = scores_series.to_dict() - scores = [[scores_series.get((player_index, repetition), 0) - for repetition in range(self.repetitions)] - for player_index in range(self.num_players)] - return scores - - @update_progress_bar - def _build_normalised_scores(self, normalised_scores_series): - normalised_scores_dict = normalised_scores_series.to_dict() - normalised_scores = [[normalised_scores_series.get((player_index, - repetition), 0) - for repetition in range(self.repetitions)] - for player_index in range(self.num_players)] - return normalised_scores + return out @update_progress_bar def _build_cooperation(self, cooperation_series): @@ -258,7 +228,7 @@ def _build_cooperation(self, cooperation_series): @update_progress_bar def _build_good_partner_matrix(self, good_partner_series): - good_partner_dict = dict(good_partner_series) + good_partner_dict = good_partner_series.to_dict() good_partner_matrix = [] for player_index in range(self.num_players): row = [] @@ -273,35 +243,19 @@ def _build_good_partner_matrix(self, good_partner_series): good_partner_matrix.append(row) return good_partner_matrix - @update_progress_bar - def _build_payoff_matrix(self): - payoff_matrix = [[0 for opponent_index in range(self.num_players)] - for player_index in range(self.num_players)] - - pairs = itertools.product(range(self.num_players), repeat=2) - - for player_index, opponent_index in pairs: - utilities = self.payoffs[player_index][opponent_index] - if utilities: - payoff_matrix[player_index][opponent_index] = np.mean(utilities) - - return payoff_matrix - - @update_progress_bar - def _build_payoff_stddevs(self): - payoff_stddevs = [[0 for opponent_index in range(self.num_players)] - for player_index in range(self.num_players)] + def _build_summary_matrix(self, attribute, func=np.mean): + matrix = [[0 for opponent_index in range(self.num_players)] + for player_index in range(self.num_players)] pairs = itertools.product(range(self.num_players), repeat=2) for player_index, opponent_index in pairs: - utilities = self.payoffs[player_index][opponent_index] + utilities = attribute[player_index][opponent_index] if utilities: - payoff_stddevs[player_index][opponent_index] = np.std(utilities) - - return payoff_stddevs + matrix[player_index][opponent_index] = func(utilities) + return matrix @update_progress_bar def _build_payoff_diffs_means(self):