1- from collections import namedtuple
1+ import csv
22import os
3+ from collections import namedtuple
34from tempfile import mkstemp
5+
46import matplotlib .pyplot as plt
57import numpy as np
68import tqdm
9+ from mpl_toolkits .axes_grid1 import make_axes_locatable
710
811import axelrod as axl
912from 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