Skip to content

Commit 0d1c47e

Browse files
Nikoleta-v3drvinceknight
authored andcommitted
Implement transitive fingerprint (#1125)
* 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.
1 parent 0692432 commit 0d1c47e

File tree

6 files changed

+355
-22
lines changed

6 files changed

+355
-22
lines changed

axelrod/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,5 @@
2020
from .tournament import Tournament
2121
from .result_set import ResultSet, ResultSetFromFile
2222
from .ecosystem import Ecosystem
23-
from .fingerprint import AshlockFingerprint
23+
from .fingerprint import AshlockFingerprint, TransitiveFingerprint
2424

axelrod/fingerprint.py

Lines changed: 199 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
from collections import namedtuple
1+
import csv
22
import os
3+
from collections import namedtuple
34
from tempfile import mkstemp
5+
46
import matplotlib.pyplot as plt
57
import numpy as np
68
import tqdm
9+
from mpl_toolkits.axes_grid1 import make_axes_locatable
710

811
import axelrod as axl
912
from axelrod import Player
@@ -154,7 +157,7 @@ def generate_data(interactions: dict, points: list, edges: list) -> dict:
154157
155158
Parameters
156159
----------
157-
interactions : dictionary
160+
interactions : dict
158161
A dictionary mapping edges to the corresponding interactions of
159162
those players.
160163
points : list
@@ -166,7 +169,7 @@ def generate_data(interactions: dict, points: list, edges: list) -> dict:
166169
167170
Returns
168171
----------
169-
point_scores : dictionary
172+
point_scores : dict
170173
A dictionary where the keys are Points of the form (x, y) and
171174
the values are the mean score for the corresponding interactions.
172175
"""
@@ -265,7 +268,7 @@ def fingerprint(
265268
self, turns: int = 50, repetitions: int = 10, step: float = 0.01,
266269
processes: int=None, filename: str = None, in_memory: bool = False,
267270
progress_bar: bool = True
268-
) -> dict:
271+
) -> dict:
269272
"""Build and play the spatial tournament.
270273
271274
Creates the probes and their edges then builds a spatial tournament.
@@ -276,16 +279,16 @@ def fingerprint(
276279
277280
Parameters
278281
----------
279-
turns : integer, optional
282+
turns : int, optional
280283
The number of turns per match
281-
repetitions : integer, optional
284+
repetitions : int, optional
282285
The number of times the round robin should be repeated
283286
step : float, optional
284287
The separation between each Point. Smaller steps will
285288
produce more Points that will be closer together.
286-
processes : integer, optional
289+
processes : int, optional
287290
The number of processes to be used for parallel processing
288-
filename: string, optional
291+
filename: str, optional
289292
The name of the file for self.spatial_tournament's interactions.
290293
if None and in_memory=False, will auto-generate a filename.
291294
in_memory: bool
@@ -296,7 +299,7 @@ def fingerprint(
296299
297300
Returns
298301
----------
299-
self.data : dictionary
302+
self.data : dict
300303
A dictionary where the keys are coordinates of the form (x, y) and
301304
the values are the mean score for the corresponding interactions.
302305
"""
@@ -379,3 +382,190 @@ def plot(self, cmap: str = 'seismic', interpolation: str = 'none',
379382
if title is not None:
380383
plt.title(title)
381384
return fig
385+
386+
387+
class TransitiveFingerprint(object):
388+
def __init__(self, strategy, opponents=None, number_of_opponents=50):
389+
"""
390+
Parameters
391+
----------
392+
strategy : class or instance
393+
A class that must be descended from axelrod.Player or an instance of
394+
axelrod.Player.
395+
opponents : list of instances
396+
A list that contains a list of opponents
397+
Default: A spectrum of Random players
398+
number_of_opponents: int
399+
The number of Random opponents
400+
Default: 50
401+
"""
402+
self.strategy = strategy
403+
404+
if opponents is None:
405+
self.opponents = [axl.Random(p) for p in
406+
np.linspace(0, 1, number_of_opponents)]
407+
else:
408+
self.opponents = opponents
409+
410+
def fingerprint(self, turns: int = 50, repetitions: int = 1000,
411+
noise: float = None, processes: int = None,
412+
filename: str = None,
413+
progress_bar: bool = True) -> np.array:
414+
"""Creates a spatial tournament to run the necessary matches to obtain
415+
fingerprint data.
416+
417+
Creates the opponents and their edges then builds a spatial tournament.
418+
419+
Parameters
420+
----------
421+
turns : int, optional
422+
The number of turns per match
423+
repetitions : int, optional
424+
The number of times the round robin should be repeated
425+
noise : float, optional
426+
The probability that a player's intended action should be flipped
427+
processes : int, optional
428+
The number of processes to be used for parallel processing
429+
filename: str, optional
430+
The name of the file for spatial tournament's interactions.
431+
if None, a filename will be generated.
432+
progress_bar : bool
433+
Whether or not to create a progress bar which will be updated
434+
435+
Returns
436+
----------
437+
self.data : np.array
438+
A numpy array containing the mean cooperation rate against each
439+
opponent in each turn. The ith row corresponds to the ith opponent
440+
and the jth column the jth turn.
441+
"""
442+
443+
if isinstance(self.strategy, axl.Player):
444+
players = [self.strategy] + self.opponents
445+
else:
446+
players = [self.strategy()] + self.opponents
447+
448+
temp_file_descriptor = None
449+
if filename is None:
450+
temp_file_descriptor, filename = mkstemp()
451+
452+
edges = [(0, k + 1) for k in range(len(self.opponents))]
453+
tournament = axl.Tournament(players=players,
454+
edges=edges, turns=turns, noise=noise,
455+
repetitions=repetitions)
456+
tournament.play(filename=filename, build_results=False,
457+
progress_bar=progress_bar, processes=processes)
458+
459+
self.data = self.analyse_cooperation_ratio(filename)
460+
461+
if temp_file_descriptor is not None:
462+
os.close(temp_file_descriptor)
463+
os.remove(filename)
464+
465+
return self.data
466+
467+
@staticmethod
468+
def analyse_cooperation_ratio(filename):
469+
"""Generates the data used from the tournament
470+
471+
Return an M by N array where M is the number of opponents and N is the
472+
number of turns.
473+
474+
Parameters
475+
----------
476+
filename : str
477+
The filename of the interactions
478+
479+
Returns
480+
----------
481+
self.data : np.array
482+
A numpy array containing the mean cooperation rate against each
483+
opponent in each turn. The ith row corresponds to the ith opponent
484+
and the jth column the jth turn.
485+
"""
486+
did_c = np.vectorize(lambda action: int(action == 'C'))
487+
488+
cooperation_rates = {}
489+
with open(filename, "r") as f:
490+
reader = csv.reader(f)
491+
for row in reader:
492+
opponent_index, player_history = int(row[1]), list(row[4])
493+
if opponent_index in cooperation_rates:
494+
cooperation_rates[opponent_index].append(did_c(player_history))
495+
else:
496+
cooperation_rates[opponent_index] = [did_c(player_history)]
497+
498+
for index, rates in cooperation_rates.items():
499+
cooperation_rates[index] = np.mean(rates, axis=0)
500+
501+
return np.array([cooperation_rates[index]
502+
for index in cooperation_rates])
503+
504+
def plot(self, cmap: str = 'viridis', interpolation: str = 'none',
505+
title: str = None, colorbar: bool = True, labels: bool = True,
506+
display_names: bool = False,
507+
ax: plt.Figure = None) -> plt.Figure:
508+
"""Plot the results of the spatial tournament.
509+
Parameters
510+
----------
511+
cmap : str, optional
512+
A matplotlib colour map, full list can be found at
513+
http://matplotlib.org/examples/color/colormaps_reference.html
514+
interpolation : str, optional
515+
A matplotlib interpolation, full list can be found at
516+
http://matplotlib.org/examples/images_contours_and_fields/interpolation_methods.html
517+
title : str, optional
518+
A title for the plot
519+
colorbar : bool, optional
520+
Choose whether the colorbar should be included or not
521+
labels : bool, optional
522+
Choose whether the axis labels and ticks should be included
523+
display_names : bool, optional
524+
Choose whether to display the names of the strategies
525+
ax: matplotlib axis
526+
Allows the plot to be written to a given matplotlib axis.
527+
Default is None.
528+
Returns
529+
----------
530+
figure : matplotlib figure
531+
A heat plot of the results of the spatial tournament
532+
"""
533+
if ax is None:
534+
fig, ax = plt.subplots()
535+
else:
536+
ax = ax
537+
538+
fig = ax.get_figure()
539+
mat = ax.imshow(self.data, cmap=cmap, interpolation=interpolation)
540+
541+
width = len(self.data) / 2
542+
height = width
543+
fig.set_size_inches(width, height)
544+
545+
plt.xlabel('turns')
546+
ax.tick_params(axis='both', which='both', length=0)
547+
548+
if display_names:
549+
plt.yticks(range(len(self.opponents)), [str(player) for player in
550+
self.opponents])
551+
else:
552+
plt.yticks([0, len(self.opponents) - 1], [0, 1])
553+
plt.ylabel("Probability of cooperation")
554+
555+
if not labels:
556+
plt.axis('off')
557+
558+
if title is not None:
559+
plt.title(title)
560+
561+
if colorbar:
562+
max_score = 0
563+
min_score = 1
564+
ticks = [min_score, 1 / 2, max_score]
565+
566+
divider = make_axes_locatable(ax)
567+
cax = divider.append_axes("right", size="5%", pad=0.2)
568+
cbar = fig.colorbar(mat, cax=cax, ticks=ticks)
569+
570+
plt.tight_layout()
571+
return fig

0 commit comments

Comments
 (0)