Skip to content

Commit

Permalink
Implement transitive fingerprint (#1125)
Browse files Browse the repository at this point in the history
* Implement transitive fingerprint

Transitive fingerprints give us another fingerprint
for the library. Is showing the cooperation rate
of a strategy against a set of opponents over the turns.

The default opponents are a list of random player with
increasing cooperation probability.

This is an implementation of fingerprints
similar to the ones used in:

- https://arxiv.org/abs/1707.06920
- https://arxiv.org/abs/1707.06307

- write tests for transitive fingerprint
- Write docstrings.
- Add documentation for transitive fingerprints
- Remove the optional matplotlib check: This was already removed elsewhere with bc952f4

* corrected typos

* fix fingerprints to fingerprint

* Add better ylabel for defaults.

* Update plot for docs.

* Add ability to pass an axes to the plot. (#7)

This means you can more easily change font size etc.

* Implement transitive fingerprint

Transitive fingerprints give us another fingerprint
for the library. Is showing the cooperation rate
of a strategy against a set of opponents over the turns.

The default opponents are a list of random player with
increasing cooperation probability.

This is an implementation of fingerprints
similar to the ones used in:

- https://arxiv.org/abs/1707.06920
- https://arxiv.org/abs/1707.06307

- write tests for transitive fingerprint
- Write docstrings.
- Add documentation for transitive fingerprints
- Remove the optional matplotlib check: This was already removed elsewhere with bc952f4

* corrected typos

* fix fingerprints to fingerprint

* Add better ylabel for defaults.

* Update plot for docs.

* fix typos pointed by Marc

* replace docstrings full word to py function

* rename number_opponents/ number_of_opponents

* Change default repetitions to 1000.

* Run smaller image to correct repetitions.
  • Loading branch information
Nikoleta-v3 authored and drvinceknight committed Aug 25, 2017
1 parent 0692432 commit 0d1c47e
Show file tree
Hide file tree
Showing 6 changed files with 355 additions and 22 deletions.
2 changes: 1 addition & 1 deletion axelrod/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@
from .tournament import Tournament
from .result_set import ResultSet, ResultSetFromFile
from .ecosystem import Ecosystem
from .fingerprint import AshlockFingerprint
from .fingerprint import AshlockFingerprint, TransitiveFingerprint

208 changes: 199 additions & 9 deletions axelrod/fingerprint.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from collections import namedtuple
import csv
import os
from collections import namedtuple
from tempfile import mkstemp

import matplotlib.pyplot as plt
import numpy as np
import tqdm
from mpl_toolkits.axes_grid1 import make_axes_locatable

import axelrod as axl
from axelrod import Player
Expand Down Expand Up @@ -154,7 +157,7 @@ def generate_data(interactions: dict, points: list, edges: list) -> dict:
Parameters
----------
interactions : dictionary
interactions : dict
A dictionary mapping edges to the corresponding interactions of
those players.
points : list
Expand All @@ -166,7 +169,7 @@ def generate_data(interactions: dict, points: list, edges: list) -> dict:
Returns
----------
point_scores : dictionary
point_scores : dict
A dictionary where the keys are Points of the form (x, y) and
the values are the mean score for the corresponding interactions.
"""
Expand Down Expand Up @@ -265,7 +268,7 @@ def fingerprint(
self, turns: int = 50, repetitions: int = 10, step: float = 0.01,
processes: int=None, filename: str = None, in_memory: bool = False,
progress_bar: bool = True
) -> dict:
) -> dict:
"""Build and play the spatial tournament.
Creates the probes and their edges then builds a spatial tournament.
Expand All @@ -276,16 +279,16 @@ def fingerprint(
Parameters
----------
turns : integer, optional
turns : int, optional
The number of turns per match
repetitions : integer, optional
repetitions : int, optional
The number of times the round robin should be repeated
step : float, optional
The separation between each Point. Smaller steps will
produce more Points that will be closer together.
processes : integer, optional
processes : int, optional
The number of processes to be used for parallel processing
filename: string, optional
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
Expand All @@ -296,7 +299,7 @@ def fingerprint(
Returns
----------
self.data : dictionary
self.data : dict
A dictionary where the keys are coordinates of the form (x, y) and
the values are the mean score for the corresponding interactions.
"""
Expand Down Expand Up @@ -379,3 +382,190 @@ def plot(self, cmap: str = 'seismic', interpolation: str = 'none',
if title is not None:
plt.title(title)
return fig


class TransitiveFingerprint(object):
def __init__(self, strategy, opponents=None, number_of_opponents=50):
"""
Parameters
----------
strategy : class or instance
A class that must be descended from axelrod.Player or an instance of
axelrod.Player.
opponents : list of instances
A list that contains a list of opponents
Default: A spectrum of Random players
number_of_opponents: int
The number of Random opponents
Default: 50
"""
self.strategy = strategy

if opponents is None:
self.opponents = [axl.Random(p) for p in
np.linspace(0, 1, number_of_opponents)]
else:
self.opponents = opponents

def fingerprint(self, turns: int = 50, repetitions: int = 1000,
noise: float = None, processes: int = None,
filename: str = None,
progress_bar: bool = True) -> np.array:
"""Creates a spatial tournament to run the necessary matches to obtain
fingerprint data.
Creates the opponents and their edges then builds a spatial tournament.
Parameters
----------
turns : int, optional
The number of turns per match
repetitions : int, optional
The number of times the round robin should be repeated
noise : float, optional
The probability that a player's intended action should be flipped
processes : int, optional
The number of processes to be used for parallel processing
filename: str, optional
The name of the file for spatial tournament's interactions.
if None, a filename will be generated.
progress_bar : bool
Whether or not to create a progress bar which will be updated
Returns
----------
self.data : np.array
A numpy array containing the mean cooperation rate against each
opponent in each turn. The ith row corresponds to the ith opponent
and the jth column the jth turn.
"""

if isinstance(self.strategy, axl.Player):
players = [self.strategy] + self.opponents
else:
players = [self.strategy()] + self.opponents

temp_file_descriptor = None
if filename is None:
temp_file_descriptor, filename = mkstemp()

edges = [(0, k + 1) for k in range(len(self.opponents))]
tournament = axl.Tournament(players=players,
edges=edges, turns=turns, noise=noise,
repetitions=repetitions)
tournament.play(filename=filename, build_results=False,
progress_bar=progress_bar, processes=processes)

self.data = self.analyse_cooperation_ratio(filename)

if temp_file_descriptor is not None:
os.close(temp_file_descriptor)
os.remove(filename)

return self.data

@staticmethod
def analyse_cooperation_ratio(filename):
"""Generates the data used from the tournament
Return an M by N array where M is the number of opponents and N is the
number of turns.
Parameters
----------
filename : str
The filename of the interactions
Returns
----------
self.data : np.array
A numpy array containing the mean cooperation rate against each
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'))

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)]

for index, rates in cooperation_rates.items():
cooperation_rates[index] = np.mean(rates, axis=0)

return np.array([cooperation_rates[index]
for index in cooperation_rates])

def plot(self, cmap: str = 'viridis', interpolation: str = 'none',
title: str = None, colorbar: bool = True, labels: bool = True,
display_names: bool = False,
ax: plt.Figure = None) -> plt.Figure:
"""Plot the results of the spatial tournament.
Parameters
----------
cmap : str, optional
A matplotlib colour map, full list can be found at
http://matplotlib.org/examples/color/colormaps_reference.html
interpolation : str, optional
A matplotlib interpolation, full list can be found at
http://matplotlib.org/examples/images_contours_and_fields/interpolation_methods.html
title : str, optional
A title for the plot
colorbar : bool, optional
Choose whether the colorbar should be included or not
labels : bool, optional
Choose whether the axis labels and ticks should be included
display_names : bool, optional
Choose whether to display the names of the strategies
ax: matplotlib axis
Allows the plot to be written to a given matplotlib axis.
Default is None.
Returns
----------
figure : matplotlib figure
A heat plot of the results of the spatial tournament
"""
if ax is None:
fig, ax = plt.subplots()
else:
ax = ax

fig = ax.get_figure()
mat = ax.imshow(self.data, cmap=cmap, interpolation=interpolation)

width = len(self.data) / 2
height = width
fig.set_size_inches(width, height)

plt.xlabel('turns')
ax.tick_params(axis='both', which='both', length=0)

if display_names:
plt.yticks(range(len(self.opponents)), [str(player) for player in
self.opponents])
else:
plt.yticks([0, len(self.opponents) - 1], [0, 1])
plt.ylabel("Probability of cooperation")

if not labels:
plt.axis('off')

if title is not None:
plt.title(title)

if colorbar:
max_score = 0
min_score = 1
ticks = [min_score, 1 / 2, max_score]

divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.2)
cbar = fig.colorbar(mat, cax=cax, ticks=ticks)

plt.tight_layout()
return fig
Loading

0 comments on commit 0d1c47e

Please sign in to comment.