From dcccd1510afd33455b7d24e28f0883f81a38fbc7 Mon Sep 17 00:00:00 2001 From: margaret Date: Tue, 11 Oct 2016 12:45:39 +0200 Subject: [PATCH 01/26] Add Prober4 class The class implements prober4 strategy from PRISON project. --- axelrod/strategies/prober.py | 51 ++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/axelrod/strategies/prober.py b/axelrod/strategies/prober.py index a6306ec7e..363101853 100644 --- a/axelrod/strategies/prober.py +++ b/axelrod/strategies/prober.py @@ -102,6 +102,57 @@ def strategy(self, opponent): return D if opponent.history[-1:] == [D] else C +class Prober4(Player): + """ + Plays fixed sequence of 20 moves initially. + If the opponent played D for D at least 3 times more than D for C, + defects forever. + Otherwise cooperates for the next 5 moves, and plays TFT afterwards. + """ + + name = 'Prober 4' + + def __init__(self): + self.probe_sequence = [C, C, D, C, D, D, D, C, C, D, + C, D, C, C, D, C, D, D, C, D] + self.probe_lenght = len(self.probe_sequence) + self.politness_pool = [C, C, C, C, C] + self.is_angry = False + + def strategy(self, opponent): + turn = len(self.history) + if turn <= self.probe_length: + return self.probe_sequence[turn - 1] + if turn == self.probe_length + 1: + self.judge(opponent) + if self.angry: + return D + else: + # Cooperate for the next 5 turns + if self.politness_pool: + return self.politness_pool.pop() + else: + # TFT + return D if opponent.history[-1:] == [D] else C + + def judge(self, opponent): + just_defect = 0 + unjust_defect = 0 + for turn in range(self.probe_lenght): + if opponent.history[turn + 1] == D: + if self.history[turn] == C: + unjust_defect += 1 + if self.history[turn] == D: + just_defect += 1 + if just_defect - unjust_defect >= 3: + self.is_angry = True + + def reset(self): + Player.reset(self) + self.politeness_pool = [C, C, C, C, C] + self.is_angry = False + + class HardProber(Player): """ Plays D, D, C, C initially. Defects forever if opponent cooperated in moves From 0de0b6e668595a4d4f3df89e581ed82469865b51 Mon Sep 17 00:00:00 2001 From: margaret Date: Tue, 11 Oct 2016 13:03:47 +0200 Subject: [PATCH 02/26] Add classifier to Prober4 --- axelrod/strategies/prober.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/axelrod/strategies/prober.py b/axelrod/strategies/prober.py index 363101853..20ebfe59b 100644 --- a/axelrod/strategies/prober.py +++ b/axelrod/strategies/prober.py @@ -111,6 +111,15 @@ class Prober4(Player): """ name = 'Prober 4' + classifier = { + 'stochastic': False, + 'memory_depth': 20, + 'makes_use_of': set(), + 'long_run_time': False, + 'inspects_source': False, + 'manipulates_source': False, + 'manipulates_state': False + } def __init__(self): self.probe_sequence = [C, C, D, C, D, D, D, C, C, D, From 3a95e765479f401e3403c9b3eacf9d6eaed43c68 Mon Sep 17 00:00:00 2001 From: margaret Date: Tue, 11 Oct 2016 13:09:17 +0200 Subject: [PATCH 03/26] Add the strategy to strategies pool --- axelrod/strategies/_strategies.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/axelrod/strategies/_strategies.py b/axelrod/strategies/_strategies.py index 59b7e941f..32dfef720 100644 --- a/axelrod/strategies/_strategies.py +++ b/axelrod/strategies/_strategies.py @@ -46,7 +46,7 @@ from .mindreader import MindReader, ProtectedMindReader, MirrorMindReader from .mutual import Desperate, Hopeless, Willing from .oncebitten import OnceBitten, FoolMeOnce, ForgetfulFoolMeOnce, FoolMeForever -from .prober import (Prober, Prober2, Prober3, HardProber, +from .prober import (Prober, Prober2, Prober3, Prober4, HardProber, NaiveProber, RemorsefulProber) from .punisher import Punisher, InversePunisher from .qlearner import RiskyQLearner, ArrogantQLearner, HesitantQLearner, CautiousQLearner @@ -164,6 +164,7 @@ Prober, Prober2, Prober3, + Prober4, ProtectedMindReader, Punisher, Raider, From 0e6b7d5b9df77ef1ddceafe010165f3b0c6a89d6 Mon Sep 17 00:00:00 2001 From: margaret Date: Tue, 11 Oct 2016 13:23:02 +0200 Subject: [PATCH 04/26] Fix bugs Add init_args decorator Add Player class init to Prober4's init Correct inconsistent variable naming Correct typos --- axelrod/strategies/prober.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/axelrod/strategies/prober.py b/axelrod/strategies/prober.py index 20ebfe59b..c5304319b 100644 --- a/axelrod/strategies/prober.py +++ b/axelrod/strategies/prober.py @@ -121,10 +121,12 @@ class Prober4(Player): 'manipulates_state': False } + @init_args def __init__(self): + Player.__init__(self) self.probe_sequence = [C, C, D, C, D, D, D, C, C, D, C, D, C, C, D, C, D, D, C, D] - self.probe_lenght = len(self.probe_sequence) + self.probe_length = len(self.probe_sequence) self.politness_pool = [C, C, C, C, C] self.is_angry = False @@ -134,7 +136,7 @@ def strategy(self, opponent): return self.probe_sequence[turn - 1] if turn == self.probe_length + 1: self.judge(opponent) - if self.angry: + if self.is_angry: return D else: # Cooperate for the next 5 turns @@ -147,7 +149,7 @@ def strategy(self, opponent): def judge(self, opponent): just_defect = 0 unjust_defect = 0 - for turn in range(self.probe_lenght): + for turn in range(self.probe_length): if opponent.history[turn + 1] == D: if self.history[turn] == C: unjust_defect += 1 From df394b488364ff262f0cb6023b42bf08a59ad9da Mon Sep 17 00:00:00 2001 From: margaret Date: Tue, 11 Oct 2016 17:11:57 +0200 Subject: [PATCH 05/26] Simplify Prober4 Calculate defections on the fly. Place the strategy logic in a single function. --- axelrod/strategies/prober.py | 59 +++++++++++++++---------------- axelrod/tests/unit/test_prober.py | 23 ++++++++++++ 2 files changed, 52 insertions(+), 30 deletions(-) diff --git a/axelrod/strategies/prober.py b/axelrod/strategies/prober.py index c5304319b..33fae5dd8 100644 --- a/axelrod/strategies/prober.py +++ b/axelrod/strategies/prober.py @@ -113,7 +113,7 @@ class Prober4(Player): name = 'Prober 4' classifier = { 'stochastic': False, - 'memory_depth': 20, + 'memory_depth': 1, 'makes_use_of': set(), 'long_run_time': False, 'inspects_source': False, @@ -124,42 +124,41 @@ class Prober4(Player): @init_args def __init__(self): Player.__init__(self) - self.probe_sequence = [C, C, D, C, D, D, D, C, C, D, - C, D, C, C, D, C, D, D, C, D] - self.probe_length = len(self.probe_sequence) - self.politness_pool = [C, C, C, C, C] + self.init_sequence = [C, C, D, C, D, D, D, C, C, D, + C, D, C, C, D, C, D, D, C, D] + self.just_Ds = 0 + self.unjust_Ds = 0 + self.politeness_pool = [C, C, C, C, C] self.is_angry = False def strategy(self, opponent): - turn = len(self.history) - if turn <= self.probe_length: - return self.probe_sequence[turn - 1] - if turn == self.probe_length + 1: - self.judge(opponent) - if self.is_angry: - return D + if len(opponent.history) == 0: + return C + if len(self.history) == 0: + return C else: - # Cooperate for the next 5 turns - if self.politness_pool: - return self.politness_pool.pop() - else: - # TFT - return D if opponent.history[-1:] == [D] else C - - def judge(self, opponent): - just_defect = 0 - unjust_defect = 0 - for turn in range(self.probe_length): - if opponent.history[turn + 1] == D: - if self.history[turn] == C: - unjust_defect += 1 - if self.history[turn] == D: - just_defect += 1 - if just_defect - unjust_defect >= 3: - self.is_angry = True + turn = len(self.history) + if turn < len(self.init_sequence): + if opponent.history[-1] == D: + if self.history[-1] == D: + self.just_Ds += 1 + if self.history[-1] == C: + self.unjust_Ds += 1 + return self.init_sequence[turn] + if turn == len(self.init_sequence): + self.is_angry = (self.just_Ds - self.unjust_Ds >= 3) + if self.is_angry: + return D + if not self.is_angry: + if self.politeness_pool: + return self.politeness_pool.pop() + else: + return D if opponent.history[-1:] == [D] else C def reset(self): Player.reset(self) + self.just_Ds = 0 + self.unjust_Ds = 0 self.politeness_pool = [C, C, C, C, C] self.is_angry = False diff --git a/axelrod/tests/unit/test_prober.py b/axelrod/tests/unit/test_prober.py index 0f2cb7c67..fe220a6c1 100644 --- a/axelrod/tests/unit/test_prober.py +++ b/axelrod/tests/unit/test_prober.py @@ -99,6 +99,29 @@ def test_strategy(self): self.responses_test([D, C, C, D, C], [C, D, C, C], [C]) +class TestProber4(TestPlayer): + + name = "Prober 4" + player = axelrod.Prober4 + expected_classifier = { + 'memory_depth': 1, + 'stochastic': False, + 'makes_use_of': set(), + 'long_run_time': False, + 'inspects_source': False, + 'manipulates_source': False, + 'manipulates_state': False + } + + def test_initial_strategy(self): + """Starts by playing CCDCDDDCCDCDCCDCDDCD.""" + self.responses_test([], [], [C, C, D, C, D, D, D, C, C, D, + C, D, C, C, D, C, D, D, C, D]) +# def test_strategy(self): +# pass +# + + class TestHardProber(TestPlayer): name = "Hard Prober" From 49e22d196d7253062a95fec8b13b2a9a5b0075e5 Mon Sep 17 00:00:00 2001 From: margaret Date: Tue, 11 Oct 2016 18:46:34 +0200 Subject: [PATCH 06/26] Add basic tests for Prober4 --- axelrod/strategies/prober.py | 16 +++++++++------- axelrod/tests/unit/test_prober.py | 22 +++++++++++++++++----- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/axelrod/strategies/prober.py b/axelrod/strategies/prober.py index 33fae5dd8..6bf7424f1 100644 --- a/axelrod/strategies/prober.py +++ b/axelrod/strategies/prober.py @@ -104,9 +104,10 @@ def strategy(self, opponent): class Prober4(Player): """ - Plays fixed sequence of 20 moves initially. - If the opponent played D for D at least 3 times more than D for C, - defects forever. + Plays initial sequence of 20 fixed moves. + Counts just (retaliated) and unjust defections of the opponent. + If the absolute difference between just and unjust defections + is greater than 2, defects forever. Otherwise cooperates for the next 5 moves, and plays TFT afterwards. """ @@ -129,7 +130,7 @@ def __init__(self): self.just_Ds = 0 self.unjust_Ds = 0 self.politeness_pool = [C, C, C, C, C] - self.is_angry = False + self.is_naughty = False def strategy(self, opponent): if len(opponent.history) == 0: @@ -146,10 +147,11 @@ def strategy(self, opponent): self.unjust_Ds += 1 return self.init_sequence[turn] if turn == len(self.init_sequence): - self.is_angry = (self.just_Ds - self.unjust_Ds >= 3) - if self.is_angry: + diff_in_Ds = abs(self.just_Ds - self.unjust_Ds) + self.is_naughty = (diff_in_Ds > 2) + if self.is_naughty: return D - if not self.is_angry: + if not self.is_naughty: if self.politeness_pool: return self.politeness_pool.pop() else: diff --git a/axelrod/tests/unit/test_prober.py b/axelrod/tests/unit/test_prober.py index fe220a6c1..71943f498 100644 --- a/axelrod/tests/unit/test_prober.py +++ b/axelrod/tests/unit/test_prober.py @@ -112,14 +112,26 @@ class TestProber4(TestPlayer): 'manipulates_source': False, 'manipulates_state': False } + initial_sequence = [C, C, D, C, D, D, D, C, C, D, + C, D, C, C, D, C, D, D, C, D] def test_initial_strategy(self): """Starts by playing CCDCDDDCCDCDCCDCDDCD.""" - self.responses_test([], [], [C, C, D, C, D, D, D, C, C, D, - C, D, C, C, D, C, D, D, C, D]) -# def test_strategy(self): -# pass -# + self.responses_test([], [], self.initial_sequence) + + def test_strategy(self): + # Defects forever if opponent played D for C + # at least 3 more times than D for D + self.responses_test(self.initial_sequence, + self.initial_sequence, [D] * 10) + + # Defects forever if opponent played D for D + # at least 3 more times than C for D + opponents_history = list(map(lambda x: D if x is C else C, + self.initial_sequence)) + + self.responses_test(self.initial_sequence, + opponents_history, [D] * 10) class TestHardProber(TestPlayer): From 9f75788fd5c73085df59a3dc77eec2eb34ebaef9 Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Thu, 13 Oct 2016 09:57:39 +0100 Subject: [PATCH 07/26] Implement state distribution in result set. Closes #738 Add state distribution and normalised state distribution to the result set. Also adds the normalised state distribution to the summary. --- axelrod/result_set.py | 62 +++++++-- axelrod/tests/unit/test_resultset.py | 127 +++++++++++++++++- .../getting_started/tournament_results.rst | 68 ++++++++-- 3 files changed, 236 insertions(+), 21 deletions(-) diff --git a/axelrod/result_set.py b/axelrod/result_set.py index 398e9b1c4..239cf6b2f 100644 --- a/axelrod/result_set.py +++ b/axelrod/result_set.py @@ -1,7 +1,7 @@ import csv import tqdm -from collections import namedtuple +from collections import namedtuple, Counter from numpy import mean, nanmedian, std from . import eigen @@ -326,6 +326,26 @@ def build_ranking(self): return sorted(range(self.nplayers), key=lambda i: -nanmedian(self.normalised_scores[i])) + def build_normalised_state_distribution(self): + """ + Returns + ---------- + + Normalised state distribution. A list of lists of counter objects: + + Dictionary where the keys are the states and the values are a + normalized counts of the number of times that state occurs. + """ + norm = [] + for player in self.state_distribution: + counters = [] + for counter in player: + total = sum(counter.values(), 0.0) + counters.append(Counter({key: value / total for key, value in + counter.items()})) + 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 @@ -350,6 +370,8 @@ def _build_empty_metrics(self, keep_interactions=False): self.cooperation = [[0 for opponent in plist] for player in plist] self.normalised_cooperation = [[[] for opponent in plist] for player in plist] + self.state_distribution = [[Counter() for opponent in plist] + for player in plist] self.good_partner_matrix = [[0 for opponent in plist] for player in plist] @@ -401,6 +423,13 @@ def _update_cooperation(self, p1, p2, cooperations): self.cooperation[p1][p2] += cooperations[0] self.cooperation[p2][p1] += cooperations[1] + def _update_state_distribution(self, p1, p2, counter): + self.state_distribution[p1][p2] += counter + + counter[('C', 'D')], counter[('D', 'C')] = (counter[('D', 'C')], + counter[('C', 'D')]) + self.state_distribution[p2][p1] += counter + def _update_good_partner_matrix(self, p1, p2, cooperations): if cooperations[0] >= cooperations[1]: self.good_partner_matrix[p1][p2] += 1 @@ -466,6 +495,7 @@ def _build_score_related_metrics(self, progress_bar=False, 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) @@ -483,6 +513,7 @@ def _build_score_related_metrics(self, progress_bar=False, self._update_normalised_scores(repetition, p1, p2, scores_per_turn) self._update_cooperation(p1, p2, cooperations) + self._update_state_distribution(p1, p2, state_counter) self._update_good_partner_matrix(p1, p2, cooperations) if progress_bar: @@ -492,6 +523,7 @@ def _build_score_related_metrics(self, progress_bar=False, self._summarise_normalised_cooperation() self.ranking = self.build_ranking() + self.normalised_state_distribution = self.build_normalised_state_distribution() self.ranked_names = self.build_ranked_names() self.payoff_matrix = self.build_payoff_matrix() self.payoff_stddevs = self.build_payoff_stddevs() @@ -548,13 +580,27 @@ def summarise(self): median_wins = map(nanmedian, self.wins) self.player = namedtuple("Player", ["Rank", "Name", "Median_score", - "Cooperation_rating", "Wins"]) + "Cooperation_rating", "Wins", + "CC_rate", "CD_rate", "DC_rate", + "DD_rate"]) + + states = [('C', 'C'), ('C', 'D'), ('D', 'C'), ('D', 'D')] + state_prob = [] + for i, player in enumerate(self.normalised_state_distribution): + counts = [] + for state in states: + counts.append(sum([opp[state] for j, opp in enumerate(player) + if i != j])) + try: + counts = [c / sum(counts) for c in counts] + except ZeroDivisionError: + counts = [0 for c in counts] + state_prob.append(counts) + + summary_data = list(zip(self.players, median_scores, + self.cooperating_rating, median_wins)) - summary_data = [perf for perf in zip(self.players, - median_scores, - self.cooperating_rating, - median_wins)] - summary_data = [self.player(rank, *summary_data[i]) for + summary_data = [self.player(rank, *summary_data[i], *state_prob[i]) for rank, i in enumerate(self.ranking)] return summary_data @@ -563,7 +609,7 @@ def write_summary(self, filename): """ Write a csv file containing summary data of the results of the form: - "Rank", "Name", "Median-score-per-turn", "Cooperation-rating" + "Rank", "Name", "Median-score-per-turn", "Cooperation-rating", "Wins", "CC-Rate", "CD-Rate", "DC-Rate", "DD-rate" Parameters ---------- diff --git a/axelrod/tests/unit/test_resultset.py b/axelrod/tests/unit/test_resultset.py index 2b3975ee4..689061bd4 100644 --- a/axelrod/tests/unit/test_resultset.py +++ b/axelrod/tests/unit/test_resultset.py @@ -2,9 +2,10 @@ import axelrod import axelrod.interaction_utils as iu -from numpy import mean, std, nanmedian +from numpy import mean, std, nanmedian, nanmean import csv +from collections import Counter from hypothesis import given, settings from axelrod.tests.property import tournaments, prob_end_tournaments @@ -120,12 +121,40 @@ def setUpClass(cls): [0, 0, 0], ] + cls.expected_state_distribution = [ + [], [], [] + ] + cls.expected_normalised_cooperation = [ [0, mean([3 / 5.0 for _ in range(3)]), mean([3 / 5.0 for _ in range(3)])], [mean([3 / 5.0 for _ in range(3)]), 0, mean([1 / 5.0 for _ in range(3)])], [0, 0, 0], ] + cls.expected_state_distribution = [ + [Counter(), + Counter({('D', 'C'): 6, ('C', 'D'): 6, ('C', 'C'): 3}), + Counter({('C', 'D'): 9, ('D', 'D'): 6})], + [Counter({('D', 'C'): 6, ('C', 'D'): 6, ('C', 'C'): 3}), + Counter(), + Counter({('D', 'D'): 12, ('C', 'D'): 3})], + [Counter({('D', 'C'): 9, ('D', 'D'): 6}), + Counter({('D', 'D'): 12, ('D', 'C'): 3}), + Counter()] + ] + + cls.expected_normalised_state_distribution = [ + [Counter(), + Counter({('D', 'C'): 0.4, ('C', 'D'): 0.4, ('C', 'C'): 0.2}), + Counter({('C', 'D'): 0.6, ('D', 'D'): 0.4})], + [Counter({('D', 'C'): 0.4, ('C', 'D'): 0.4, ('C', 'C'): 0.2}), + Counter(), + Counter({('D', 'D'): 0.8, ('C', 'D'): 0.2})], + [Counter({('D', 'C'): 0.6, ('D', 'D'): 0.4}), + Counter({('D', 'D'): 0.8, ('D', 'C'): 0.2}), + Counter()] + ] + cls.expected_vengeful_cooperation = [[2 * element - 1 for element in row] for row in cls.expected_normalised_cooperation] @@ -323,6 +352,22 @@ def test_normalised_cooperation(self): self.assertEqual(rs.normalised_cooperation, self.expected_normalised_cooperation) + def test_state_distribution(self): + rs = axelrod.ResultSet(self.players, self.interactions, + progress_bar=False) + self.assertIsInstance(rs.state_distribution, list) + self.assertEqual(len(rs.state_distribution), rs.nplayers) + self.assertEqual(rs.state_distribution, + self.expected_state_distribution) + + def test_state_normalised_distribution(self): + rs = axelrod.ResultSet(self.players, self.interactions, + progress_bar=False) + self.assertIsInstance(rs.normalised_state_distribution, list) + self.assertEqual(len(rs.normalised_state_distribution), rs.nplayers) + self.assertEqual(rs.normalised_state_distribution, + self.expected_normalised_state_distribution) + def test_vengeful_cooperation(self): rs = axelrod.ResultSet(self.players, self.interactions, progress_bar=False) @@ -413,6 +458,9 @@ def test_summarise(self): self.assertEqual([float(player.Wins) for player in sd], ranked_median_wins) + for player in sd: + self.assertEqual(player.CC_rate + player.CD_rate + player.DC_rate + player.DD_rate, 1) + def test_write_summary(self): rs = axelrod.ResultSet(self.players, self.interactions, progress_bar=False) @@ -422,13 +470,11 @@ def test_write_summary(self): csvreader = csv.reader(csvfile) for row in csvreader: ranked_names.append(row[1]) - self.assertEqual(len(row), 5) + self.assertEqual(len(row), 9) self.assertEqual(ranked_names[0], "Name") self.assertEqual(ranked_names[1:], rs.ranked_names) - - class TestResultSetFromFile(unittest.TestCase): filename = "test_outputs/test_results_from_file.csv" players = [axelrod.Cooperator(), @@ -794,6 +840,26 @@ def setUpClass(cls): 0.5488212999484519 ] + cls.expected_state_distribution = [ + [Counter(), + Counter({('C', 'C'): 3, ('C', 'D'): 6, ('D', 'C'): 6}), + Counter({('C', 'D'): 9, ('D', 'D'): 6})], + [Counter({('C', 'C'): 3, ('C', 'D'): 6, ('D', 'C'): 6}), + Counter(), + Counter()], + [Counter({('D', 'C'): 9, ('D', 'D'): 6}), Counter(), Counter()] + ] + + cls.expected_normalised_state_distribution = [ + [Counter(), + Counter({('C', 'C'): 0.2, ('C', 'D'): 0.4, ('D', 'C'): 0.4}), + Counter({('C', 'D'): 0.6, ('D', 'D'): 0.4})], + [Counter({('C', 'C'): 0.2, ('C', 'D'): 0.4, ('D', 'C'): 0.4}), + Counter(), + Counter()], + [Counter({('D', 'C'): 0.6, ('D', 'D'): 0.4}), Counter(), Counter()] + ] + cls.expected_csv = ( 'Defector,Tit For Tat,Alternator\n3.4,2.6,1.5\n3.4,2.6,1.5\n3.4,2.6,1.5\n') @@ -1000,6 +1066,32 @@ def setUpClass(cls): 0.1633132292825755 ] + cls.expected_state_distribution = [ + [Counter(), + Counter({('C', 'C'): 3, ('C', 'D'): 6, ('D', 'C'): 6}), + Counter(), + Counter()], + [Counter({('C', 'C'): 3, ('C', 'D'): 6, ('D', 'C'): 6}), + Counter(), + Counter(), + Counter()], + [Counter(), Counter(), Counter(), Counter({('D', 'C'): 15})], + [Counter(), Counter(), Counter({('C', 'D'): 15}), Counter()] + ] + + cls.expected_normalised_state_distribution = [ + [Counter(), + Counter({('C', 'C'): 0.2, ('C', 'D'): 0.4, ('D', 'C'): 0.4}), + Counter(), + Counter()], + [Counter({('C', 'C'): 0.2, ('C', 'D'): 0.4, ('D', 'C'): 0.4}), + Counter(), + Counter(), + Counter()], + [Counter(), Counter(), Counter(), Counter({('D', 'C'): 1.0})], + [Counter(), Counter(), Counter({('C', 'D'): 1.0}), Counter()] + ] + cls.expected_csv = ( "Defector,Alternator,Tit For Tat,Cooperator\n5.0,2.6,2.6,0.0\n5.0,2.6,2.6,0.0\n5.0,2.6,2.6,0.0\n") @@ -1131,9 +1223,36 @@ def setUpClass(cls): 0.3985944056208427 ] + cls.expected_state_distribution = [ + [Counter(), Counter(), Counter(), Counter()], + [Counter(), Counter(), Counter(), Counter()], + [Counter(), Counter(), Counter(), Counter()], + [Counter(), Counter(), Counter(), Counter()] + ] + + cls.expected_normalised_state_distribution = [ + [Counter(), Counter(), Counter(), Counter()], + [Counter(), Counter(), Counter(), Counter()], + [Counter(), Counter(), Counter(), Counter()], + [Counter(), Counter(), Counter(), Counter()] + ] + + cls.expected_csv = ( 'Alternator,Tit For Tat,Defector,Cooperator\nnan,nan,nan,nan\nnan,nan,nan,nan\nnan,nan,nan,nan\n') def test_equality(self): """Overwriting for this particular case""" pass + + def test_summarise(self): + """Overwriting for this particular case""" + rs = axelrod.ResultSet(self.players, self.interactions, + progress_bar=False) + sd = rs.summarise() + + for player in sd: + self.assertEqual(player.CC_rate, 0) + self.assertEqual(player.CD_rate, 0) + self.assertEqual(player.DC_rate, 0) + self.assertEqual(player.DD_rate, 0) diff --git a/docs/tutorials/getting_started/tournament_results.rst b/docs/tutorials/getting_started/tournament_results.rst index 8fb2c52dd..6bbcc94b7 100644 --- a/docs/tutorials/getting_started/tournament_results.rst +++ b/docs/tutorials/getting_started/tournament_results.rst @@ -20,6 +20,10 @@ This tutorial will show you how to access the various results of a tournament: - Payoff difference means: the mean score differences. - Cooperation counts: the number of times each player cooperated. - Normalised cooperation: cooperation count per turn. +- Normalised cooperation: cooperation count per turn. +- State distribution: the count of each type of state of a match +- Normalised state distribution: the normalised count of each type of state of a + match - Cooperation rating: cooperation rating of each player - Vengeful cooperation: a morality metric from the literature (see :ref:`morality-metrics`). @@ -210,6 +214,52 @@ We see that :code:`Cooperator` for all the rounds (as expected):: >>> results.normalised_cooperation[0] [1.0, 1.0, 1.0, 1.0] +State distribution counts +------------------------- + +This gives a total state count against each opponent:: + + >>> pprint.pprint(results.state_distribution) + [[Counter(), + Counter({('C', 'D'): 30}), + Counter({('C', 'C'): 30}), + Counter({('C', 'C'): 30})], + [Counter({('D', 'C'): 30}), + Counter(), + Counter({('D', 'D'): 27, ('D', 'C'): 3}), + Counter({('D', 'D'): 27, ('D', 'C'): 3})], + [Counter({('C', 'C'): 30}), + Counter({('D', 'D'): 27, ('C', 'D'): 3}), + Counter(), + Counter({('C', 'C'): 30})], + [Counter({('C', 'C'): 30}), + Counter({('D', 'D'): 27, ('C', 'D'): 3}), + Counter({('C', 'C'): 30}), + Counter()]] + +Normalised state distribution +----------------------------- + +This gives the average rate state distribution against each opponent:: + + >>> pprint.pprint(results.normalised_state_distribution) + [[Counter(), + Counter({('C', 'D'): 1.0}), + Counter({('C', 'C'): 1.0}), + 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({('C', 'C'): 1.0}), + 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({('C', 'C'): 1.0}), + Counter()]] + Morality Metrics ---------------- @@ -242,10 +292,10 @@ that summarises the results of the tournament:: >>> summary = results.summarise() >>> pprint.pprint(summary) - [Player(Rank=0, Name='Defector', Median_score=2.6..., Cooperation_rating=0.0, Wins=3.0), - Player(Rank=1, Name='Tit For Tat', Median_score=2.3..., Cooperation_rating=0.7, Wins=0.0), - Player(Rank=2, Name='Grudger', Median_score=2.3..., Cooperation_rating=0.7, Wins=0.0), - Player(Rank=3, Name='Cooperator', Median_score=2.0..., Cooperation_rating=1.0, Wins=0.0)] + [Player(Rank=0, Name='Defector', Median_score=2.6..., Cooperation_rating=0.0, Wins=3.0, CC_rate=...), + Player(Rank=1, Name='Tit For Tat', Median_score=2.3..., Cooperation_rating=0.7, Wins=0.0, CC_rate=...), + Player(Rank=2, Name='Grudger', Median_score=2.3..., Cooperation_rating=0.7, Wins=0.0, CC_rate=...), + Player(Rank=3, Name='Cooperator', Median_score=2.0..., Cooperation_rating=1.0, Wins=0.0, CC_rate=...)] It is also possible to write this data directly to a csv file using the `write_summary` method:: @@ -256,8 +306,8 @@ It is also possible to write this data directly to a csv file using the ... csvreader = csv.reader(outfile) ... for row in csvreader: ... print(row) - ['Rank', 'Name', 'Median_score', 'Cooperation_rating', 'Wins'] - ['0', 'Defector', '2.6...', '0.0', '3.0'] - ['1', 'Tit For Tat', '2.3...', '0.7', '0.0'] - ['2', 'Grudger', '2.3...', '0.7', '0.0'] - ['3', 'Cooperator', '2.0...', '1.0', '0.0'] + ['Rank', 'Name', 'Median_score', 'Cooperation_rating', 'Wins', 'CC_rate', 'CD_rate', 'DC_rate', 'DD_rate'] + ['0', 'Defector', '2.6...', '0.0', '3.0', ...] + ['1', 'Tit For Tat', '2.3...', '0.7', '0.0', ...] + ['2', 'Grudger', '2.3...', '0.7', '0.0', ...] + ['3', 'Cooperator', '2.0...', '1.0', '0.0', ...] From a8975d3bfea9c40df166e2fb7992f28f930f3adc Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Fri, 14 Oct 2016 11:08:26 +0100 Subject: [PATCH 08/26] Fix for py2. --- axelrod/result_set.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/axelrod/result_set.py b/axelrod/result_set.py index 239cf6b2f..6f5278cc4 100644 --- a/axelrod/result_set.py +++ b/axelrod/result_set.py @@ -600,7 +600,8 @@ def summarise(self): summary_data = list(zip(self.players, median_scores, self.cooperating_rating, median_wins)) - summary_data = [self.player(rank, *summary_data[i], *state_prob[i]) for + summary_data = [self.player(rank, *(list(summary_data[i]) + + state_prob[i])) for rank, i in enumerate(self.ranking)] return summary_data From d12ea39a7d68379d1a1812bf5c56880d346715bf Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Fri, 14 Oct 2016 15:43:11 +0100 Subject: [PATCH 09/26] Remove unused import. --- axelrod/tests/unit/test_resultset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axelrod/tests/unit/test_resultset.py b/axelrod/tests/unit/test_resultset.py index 689061bd4..46b0aa7f3 100644 --- a/axelrod/tests/unit/test_resultset.py +++ b/axelrod/tests/unit/test_resultset.py @@ -2,7 +2,7 @@ import axelrod import axelrod.interaction_utils as iu -from numpy import mean, std, nanmedian, nanmean +from numpy import mean, std, nanmedian import csv from collections import Counter From 3882628b8f2cd862be411a3dac74197327f61279 Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Fri, 14 Oct 2016 15:47:43 +0100 Subject: [PATCH 10/26] Remove further unused lines. --- axelrod/tests/unit/test_resultset.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/axelrod/tests/unit/test_resultset.py b/axelrod/tests/unit/test_resultset.py index 46b0aa7f3..601e50ecd 100644 --- a/axelrod/tests/unit/test_resultset.py +++ b/axelrod/tests/unit/test_resultset.py @@ -83,7 +83,6 @@ def setUpClass(cls): [[17/5.0 for _ in range(3)], [9/5.0 for _ in range(3)], []] ] - norm_scores = cls.expected_normalised_scores cls.expected_score_diffs = [ [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], @@ -763,7 +762,6 @@ def setUpClass(cls): [[17/5.0 for _ in range(3)], [], []] ] - norm_scores = cls.expected_normalised_scores cls.expected_score_diffs = [ [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], @@ -860,9 +858,6 @@ def setUpClass(cls): [Counter({('D', 'C'): 0.6, ('D', 'D'): 0.4}), Counter(), Counter()] ] - cls.expected_csv = ( - 'Defector,Tit For Tat,Alternator\n3.4,2.6,1.5\n3.4,2.6,1.5\n3.4,2.6,1.5\n') - def test_match_lengths(self): """ Overwriting match lengths test. This method, among other things, checks @@ -972,7 +967,6 @@ def setUpClass(cls): [[], [], [0 for _ in range(3)], []] ] - norm_scores = cls.expected_normalised_scores cls.expected_score_diffs = [ [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], @@ -1092,9 +1086,6 @@ def setUpClass(cls): [Counter(), Counter(), Counter({('C', 'D'): 1.0}), Counter()] ] - cls.expected_csv = ( - "Defector,Alternator,Tit For Tat,Cooperator\n5.0,2.6,2.6,0.0\n5.0,2.6,2.6,0.0\n5.0,2.6,2.6,0.0\n") - class TestResultSetSpatialStructureThree(TestResultSetSpatialStructure): @@ -1159,7 +1150,6 @@ def setUpClass(cls): [[], [], [], [15 /5.0 for _ in range(3)]] ] - norm_scores = cls.expected_normalised_scores cls.expected_score_diffs = [ [[0.0 for _ in range(3)] for _ in range(4) ] for _ in range(4) ] @@ -1238,9 +1228,6 @@ def setUpClass(cls): ] - cls.expected_csv = ( - 'Alternator,Tit For Tat,Defector,Cooperator\nnan,nan,nan,nan\nnan,nan,nan,nan\nnan,nan,nan,nan\n') - def test_equality(self): """Overwriting for this particular case""" pass From df644282df3ca59652353dfe35cc29528534c3e5 Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Fri, 14 Oct 2016 18:58:55 +0100 Subject: [PATCH 11/26] Make doctests more verbose. --- docs/tutorials/getting_started/tournament_results.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/tutorials/getting_started/tournament_results.rst b/docs/tutorials/getting_started/tournament_results.rst index 6bbcc94b7..baff9d51b 100644 --- a/docs/tutorials/getting_started/tournament_results.rst +++ b/docs/tutorials/getting_started/tournament_results.rst @@ -307,7 +307,7 @@ It is also possible to write this data directly to a csv file using the ... for row in csvreader: ... print(row) ['Rank', 'Name', 'Median_score', 'Cooperation_rating', 'Wins', 'CC_rate', 'CD_rate', 'DC_rate', 'DD_rate'] - ['0', 'Defector', '2.6...', '0.0', '3.0', ...] - ['1', 'Tit For Tat', '2.3...', '0.7', '0.0', ...] - ['2', 'Grudger', '2.3...', '0.7', '0.0', ...] - ['3', 'Cooperator', '2.0...', '1.0', '0.0', ...] + ['0', 'Defector', '2.6...', '0.0', '3.0', '0.0', '0.0', '0.4...', '0.6...'] + ['1', 'Tit For Tat', '2.3...', '0.7', '0.0', '0.66...', '0.03...', '0.0', '0.3...'] + ['2', 'Grudger', '2.3...', '0.7', '0.0', '0.66...', '0.03...', '0.0', '0.3...'] + ['3', 'Cooperator', '2.0...', '1.0', '0.0', '0.66...', '0.33...', '0.0', '0.0'] From 9a1ab8f2e9c785bbf16149763a445b4827ada01e Mon Sep 17 00:00:00 2001 From: margaret Date: Sat, 15 Oct 2016 11:04:37 +0200 Subject: [PATCH 12/26] Add more tests to Prober4 Tweak Prober4 property names --- axelrod/strategies/prober.py | 22 ++++++------ axelrod/tests/unit/test_prober.py | 56 ++++++++++++++++++++++++------- 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/axelrod/strategies/prober.py b/axelrod/strategies/prober.py index 6bf7424f1..3ed8851d2 100644 --- a/axelrod/strategies/prober.py +++ b/axelrod/strategies/prober.py @@ -104,7 +104,7 @@ def strategy(self, opponent): class Prober4(Player): """ - Plays initial sequence of 20 fixed moves. + Plays fixed initial sequence of 20 moves. Counts just (retaliated) and unjust defections of the opponent. If the absolute difference between just and unjust defections is greater than 2, defects forever. @@ -129,8 +129,8 @@ def __init__(self): C, D, C, C, D, C, D, D, C, D] self.just_Ds = 0 self.unjust_Ds = 0 - self.politeness_pool = [C, C, C, C, C] - self.is_naughty = False + self.cooperation_pool = 5 * [C] + self.turned_defector = False def strategy(self, opponent): if len(opponent.history) == 0: @@ -148,21 +148,21 @@ def strategy(self, opponent): return self.init_sequence[turn] if turn == len(self.init_sequence): diff_in_Ds = abs(self.just_Ds - self.unjust_Ds) - self.is_naughty = (diff_in_Ds > 2) - if self.is_naughty: + self.turned_defector = (diff_in_Ds > 2) + if self.turned_defector: return D - if not self.is_naughty: - if self.politeness_pool: - return self.politeness_pool.pop() + if not self.turned_defector: + if self.cooperation_pool: + return self.cooperation_pool.pop() else: - return D if opponent.history[-1:] == [D] else C + return D if opponent.history[-1] == D else C def reset(self): Player.reset(self) self.just_Ds = 0 self.unjust_Ds = 0 - self.politeness_pool = [C, C, C, C, C] - self.is_angry = False + self.cooperation_pool = [C, C, C, C, C] + self.became_defector = False class HardProber(Player): diff --git a/axelrod/tests/unit/test_prober.py b/axelrod/tests/unit/test_prober.py index 71943f498..b526a83ef 100644 --- a/axelrod/tests/unit/test_prober.py +++ b/axelrod/tests/unit/test_prober.py @@ -104,8 +104,8 @@ class TestProber4(TestPlayer): name = "Prober 4" player = axelrod.Prober4 expected_classifier = { - 'memory_depth': 1, 'stochastic': False, + 'memory_depth': 1, 'makes_use_of': set(), 'long_run_time': False, 'inspects_source': False, @@ -114,24 +114,54 @@ class TestProber4(TestPlayer): } initial_sequence = [C, C, D, C, D, D, D, C, C, D, C, D, C, C, D, C, D, D, C, D] + cooperation_pool = [C] * 5 def test_initial_strategy(self): """Starts by playing CCDCDDDCCDCDCCDCDDCD.""" self.responses_test([], [], self.initial_sequence) def test_strategy(self): - # Defects forever if opponent played D for C - # at least 3 more times than D for D - self.responses_test(self.initial_sequence, - self.initial_sequence, [D] * 10) - - # Defects forever if opponent played D for D - # at least 3 more times than C for D - opponents_history = list(map(lambda x: D if x is C else C, - self.initial_sequence)) - - self.responses_test(self.initial_sequence, - opponents_history, [D] * 10) + # Defects forever if the opponent played D for C + # at least 3 times more than D for D + history1 = self.initial_sequence + history2 = self.initial_sequence + responses = [D] * 10 + self.responses_test(history1, history2, responses) + + # or if the opponent played D for D + # at least 3 times more than D for C + history1 = self.initial_sequence + history2 = list(map(lambda x: D if x is C else C, + self.initial_sequence)) + responses = [D] * 10 + self.responses_test(history1, history2, responses) + + # Otherwise cooperates for 5 rounds + history1 = self.initial_sequence + history2 = [C] * len(history1) + responses = self.cooperation_pool + self.responses_test(history1, history2, responses) + + # and plays like TFT afterwards + history1 = self.initial_sequence + self.cooperation_pool + history2 = [C] * (len(history1) - 1) + [D] + self.responses_test(history1, history2, [D]) + + history1 = self.initial_sequence + self.cooperation_pool + [D] + history2 = [C] * len(history1) + self.responses_test(history1, history2, [C]) + + history1 = self.initial_sequence + self.cooperation_pool + history2 = [C] * len(history1) + self.responses_test(history1, history2, [C]) + + history1 = self.initial_sequence + self.cooperation_pool + [C] + history2 = [C] * (len(history1) - 1) + [D] + self.responses_test(history1, history2, [D]) + + history1 = self.initial_sequence + self.cooperation_pool + [C] + history2 = [C] * (len(history1) - 1) + [D] + self.responses_test(history1, history2, [D]) class TestHardProber(TestPlayer): From cbb21bd03c23aee2545d0d7c8ae1418bc532f185 Mon Sep 17 00:00:00 2001 From: margaret Date: Sat, 15 Oct 2016 16:04:11 +0200 Subject: [PATCH 13/26] Review all commits Fix bug - reverse self.turned_defector condition. Refine docstring. Tweak formatting. Update parameter name in reset method. Rewrite tests to loop over set of histories. --- axelrod/strategies/prober.py | 31 ++++++++------ axelrod/tests/unit/test_prober.py | 70 +++++++++++++++++-------------- 2 files changed, 57 insertions(+), 44 deletions(-) diff --git a/axelrod/strategies/prober.py b/axelrod/strategies/prober.py index 3ed8851d2..c4655aaf6 100644 --- a/axelrod/strategies/prober.py +++ b/axelrod/strategies/prober.py @@ -104,11 +104,15 @@ def strategy(self, opponent): class Prober4(Player): """ - Plays fixed initial sequence of 20 moves. - Counts just (retaliated) and unjust defections of the opponent. - If the absolute difference between just and unjust defections - is greater than 2, defects forever. - Otherwise cooperates for the next 5 moves, and plays TFT afterwards. + Plays C, C, D, C, D, D, D, C, C, D, C, D, C, C, D, C, D, D, C, D initially. + Counts retaliating and provocative defections of the opponent. + If the absolute difference between the counts is smaller or equal to 2, + defects forever. + Otherwise plays C for the next 5 turns and TFT for the rest of the game. + + Names: + + - prober4: [PRISON1998]_ """ name = 'Prober 4' @@ -125,18 +129,19 @@ class Prober4(Player): @init_args def __init__(self): Player.__init__(self) - self.init_sequence = [C, C, D, C, D, D, D, C, C, D, - C, D, C, C, D, C, D, D, C, D] + self.init_sequence = [ + C, C, D, C, D, D, D, C, C, D, C, D, C, C, D, C, D, D, C, D + ] self.just_Ds = 0 self.unjust_Ds = 0 - self.cooperation_pool = 5 * [C] + self.cooperation_pool = [C] * 5 self.turned_defector = False def strategy(self, opponent): if len(opponent.history) == 0: - return C + return self.init_sequence[0] if len(self.history) == 0: - return C + return self.init_sequence[0] else: turn = len(self.history) if turn < len(self.init_sequence): @@ -148,7 +153,7 @@ def strategy(self, opponent): return self.init_sequence[turn] if turn == len(self.init_sequence): diff_in_Ds = abs(self.just_Ds - self.unjust_Ds) - self.turned_defector = (diff_in_Ds > 2) + self.turned_defector = (diff_in_Ds <= 2) if self.turned_defector: return D if not self.turned_defector: @@ -161,8 +166,8 @@ def reset(self): Player.reset(self) self.just_Ds = 0 self.unjust_Ds = 0 - self.cooperation_pool = [C, C, C, C, C] - self.became_defector = False + self.cooperation_pool = [C] * 5 + self.turned_defector = False class HardProber(Player): diff --git a/axelrod/tests/unit/test_prober.py b/axelrod/tests/unit/test_prober.py index b526a83ef..726ab3f4e 100644 --- a/axelrod/tests/unit/test_prober.py +++ b/axelrod/tests/unit/test_prober.py @@ -112,8 +112,9 @@ class TestProber4(TestPlayer): 'manipulates_source': False, 'manipulates_state': False } - initial_sequence = [C, C, D, C, D, D, D, C, C, D, - C, D, C, C, D, C, D, D, C, D] + initial_sequence = [ + C, C, D, C, D, D, D, C, C, D, C, D, C, C, D, C, D, D, C, D + ] cooperation_pool = [C] * 5 def test_initial_strategy(self): @@ -121,47 +122,54 @@ def test_initial_strategy(self): self.responses_test([], [], self.initial_sequence) def test_strategy(self): - # Defects forever if the opponent played D for C - # at least 3 times more than D for D - history1 = self.initial_sequence - history2 = self.initial_sequence - responses = [D] * 10 - self.responses_test(history1, history2, responses) + # After playing the initial sequence defects forever + # if the absolute difference in the number of retaliating + # and provocative defections of the opponent is smaller or equal to 2 + + provocative_histories = [ + [C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C], + [C, D, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C], + [C, D, C, D, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C], + [C, C, D, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C], + [C, C, D, C, D, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C], + [D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D], + ] - # or if the opponent played D for D - # at least 3 times more than D for C history1 = self.initial_sequence - history2 = list(map(lambda x: D if x is C else C, - self.initial_sequence)) responses = [D] * 10 - self.responses_test(history1, history2, responses) + for history2 in provocative_histories: + self.responses_test(history1, history2, responses) # Otherwise cooperates for 5 rounds + unprovocative_histories = [ + [C, C, D, C, D, D, D, C, C, D, C, D, C, C, D, C, D, D, C, D], + [D, D, C, D, C, C, C, D, D, C, D, C, D, D, C, D, C, C, D, C], + [C, C, D, C, D, D, C, C, C, C, C, C, C, C, C, C, C, C, C, C], + [C, C, D, C, D, D, C, C, D, C, C, C, C, C, C, D, D, D, C, C], + [C, C, C, C, D, D, C, C, D, C, C, D, D, C, D, C, D, C, C, C], + ] + history1 = self.initial_sequence - history2 = [C] * len(history1) responses = self.cooperation_pool - self.responses_test(history1, history2, responses) + for history2 in unprovocative_histories: + self.responses_test(history1, history2, responses) # and plays like TFT afterwards - history1 = self.initial_sequence + self.cooperation_pool - history2 = [C] * (len(history1) - 1) + [D] - self.responses_test(history1, history2, [D]) - - history1 = self.initial_sequence + self.cooperation_pool + [D] - history2 = [C] * len(history1) - self.responses_test(history1, history2, [C]) + history1 += self.cooperation_pool + history2 += self.cooperation_pool + self.responses_test(history1, history2, [C]) - history1 = self.initial_sequence + self.cooperation_pool - history2 = [C] * len(history1) - self.responses_test(history1, history2, [C]) + history1 += [C] + history2 += [D] + self.responses_test(history1, history2, [D]) - history1 = self.initial_sequence + self.cooperation_pool + [C] - history2 = [C] * (len(history1) - 1) + [D] - self.responses_test(history1, history2, [D]) + history1 += [D] + history2 += [C] + self.responses_test(history1, history2, [C]) - history1 = self.initial_sequence + self.cooperation_pool + [C] - history2 = [C] * (len(history1) - 1) + [D] - self.responses_test(history1, history2, [D]) + history1 += [C] + history2 += [D] + self.responses_test(history1, history2, [D]) class TestHardProber(TestPlayer): From 8e9126f41391e4d24982589f113ab94e5429b89d Mon Sep 17 00:00:00 2001 From: margaret Date: Sat, 15 Oct 2016 16:56:50 +0200 Subject: [PATCH 14/26] Update strategies counter --- docs/tutorials/advanced/classification_of_strategies.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/advanced/classification_of_strategies.rst b/docs/tutorials/advanced/classification_of_strategies.rst index b863ef2e5..d6e478986 100644 --- a/docs/tutorials/advanced/classification_of_strategies.rst +++ b/docs/tutorials/advanced/classification_of_strategies.rst @@ -58,7 +58,7 @@ make a decision:: ... } >>> strategies = axl.filtered_strategies(filterset) >>> len(strategies) - 24 + 25 Multiple filters can be specified within the filterset dictionary. To specify a range of memory_depth values, we can use the 'min_memory_depth' and @@ -70,7 +70,7 @@ range of memory_depth values, we can use the 'min_memory_depth' and ... } >>> strategies = axl.filtered_strategies(filterset) >>> len(strategies) - 41 + 42 We can also identify strategies that make use of particular properties of the tournament. For example, here is the number of strategies that make use of the From 7ff76dff6e42e538d2a1e940eb0559ff53723323 Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Sat, 15 Oct 2016 17:45:44 +0100 Subject: [PATCH 15/26] Fix progress bar for "finishing". --- axelrod/result_set.py | 14 +++++++++++++- axelrod/tests/unit/test_resultset.py | 6 ++++-- axelrod/tests/unit/test_tournament.py | 6 ++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/axelrod/result_set.py b/axelrod/result_set.py index 6f5278cc4..9ecaa43b6 100644 --- a/axelrod/result_set.py +++ b/axelrod/result_set.py @@ -141,6 +141,7 @@ def _build_players(self): del self.players_d # Manual garbage collection return players + @update_progress_bar def build_eigenmoses_rating(self): """ Returns: @@ -154,6 +155,7 @@ def build_eigenmoses_rating(self): return eigenvector.tolist() + @update_progress_bar def build_eigenjesus_rating(self): """ Returns: @@ -167,6 +169,7 @@ def build_eigenjesus_rating(self): return eigenvector.tolist() + @update_progress_bar def build_cooperating_rating(self): """ Returns: @@ -197,6 +200,7 @@ def build_cooperating_rating(self): return [sum(cs) / max(1, float(sum(ls))) for cs, ls in zip(self.cooperation, lengths)] + @update_progress_bar def build_vengeful_cooperation(self): """ Returns: @@ -210,6 +214,7 @@ def build_vengeful_cooperation(self): 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: @@ -231,6 +236,7 @@ def build_payoff_diffs_means(self): for player in self.score_diffs] return payoff_diffs_means + @update_progress_bar def build_payoff_stddevs(self): """ Returns: @@ -267,6 +273,7 @@ def build_payoff_stddevs(self): return payoff_stddevs + @update_progress_bar def build_payoff_matrix(self): """ Returns: @@ -301,6 +308,7 @@ def build_payoff_matrix(self): return payoff_matrix + @update_progress_bar def build_ranked_names(self): """ Returns: @@ -311,6 +319,7 @@ def build_ranked_names(self): return [str(self.players[i]) for i in self.ranking] + @update_progress_bar def build_ranking(self): """ Returns: @@ -326,6 +335,7 @@ def build_ranking(self): return sorted(range(self.nplayers), key=lambda i: -nanmedian(self.normalised_scores[i])) + @update_progress_bar def build_normalised_state_distribution(self): """ Returns @@ -517,7 +527,7 @@ def _build_score_related_metrics(self, progress_bar=False, self._update_good_partner_matrix(p1, p2, cooperations) if progress_bar: - self.progress_bar = tqdm.tqdm(total=10 + 2 * self.nplayers, + self.progress_bar = tqdm.tqdm(total=11 + 2 * self.nplayers, desc="Finishing") self._summarise_normalised_scores() self._summarise_normalised_cooperation() @@ -648,6 +658,8 @@ def read_match_chunks(self, progress_bar=False): 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: diff --git a/axelrod/tests/unit/test_resultset.py b/axelrod/tests/unit/test_resultset.py index 601e50ecd..7029b6d95 100644 --- a/axelrod/tests/unit/test_resultset.py +++ b/axelrod/tests/unit/test_resultset.py @@ -223,12 +223,14 @@ def test_init_with_different_game(self): def test_with_progress_bar(self): rs = axelrod.ResultSet(self.players, self.interactions) self.assertTrue(rs.progress_bar) - self.assertEqual(rs.progress_bar.total, 10 + 2 * rs.nplayers) + self.assertEqual(rs.progress_bar.total, 11 + 2 * rs.nplayers) + self.assertEqual(rs.progress_bar.n, rs.progress_bar.total) rs = axelrod.ResultSet(self.players, self.interactions, progress_bar=True) self.assertTrue(rs.progress_bar) - self.assertEqual(rs.progress_bar.total, 10 + 2 * rs.nplayers) + self.assertEqual(rs.progress_bar.total, 11 + 2 * rs.nplayers) + self.assertEqual(rs.progress_bar.n, rs.progress_bar.total) def test_match_lengths(self): rs = axelrod.ResultSet(self.players, self.interactions, diff --git a/axelrod/tests/unit/test_tournament.py b/axelrod/tests/unit/test_tournament.py index 2ae78303d..3b4a042c7 100644 --- a/axelrod/tests/unit/test_tournament.py +++ b/axelrod/tests/unit/test_tournament.py @@ -176,10 +176,14 @@ def test_progress_bar_play(self): results = tournament.play() self.assertIsInstance(results, axelrod.ResultSet) self.assertEqual(tournament.progress_bar.total, 15) + self.assertEqual(tournament.progress_bar.total, + tournament.progress_bar.n) results = tournament.play(progress_bar=True) self.assertIsInstance(results, axelrod.ResultSet) self.assertEqual(tournament.progress_bar.total, 15) + self.assertEqual(tournament.progress_bar.total, + tournament.progress_bar.n) # Test without build results results = tournament.play(progress_bar=True, build_results=False, @@ -188,6 +192,8 @@ def test_progress_bar_play(self): results = axelrod.ResultSetFromFile(self.filename) self.assertIsInstance(results, axelrod.ResultSet) self.assertEqual(tournament.progress_bar.total, 15) + self.assertEqual(tournament.progress_bar.total, + tournament.progress_bar.n) @unittest.skipIf(axelrod.on_windows, "Parallel processing not supported on Windows") From 99dc6f9257c14723aef7fa29c28873fc9a02a202 Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Sat, 15 Oct 2016 19:21:18 +0100 Subject: [PATCH 16/26] Remove unneeded test calls with progress bar. --- axelrod/tests/unit/test_resultset.py | 20 +++++++++++--------- axelrod/tests/unit/test_tournament.py | 4 ++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/axelrod/tests/unit/test_resultset.py b/axelrod/tests/unit/test_resultset.py index 7029b6d95..f55073799 100644 --- a/axelrod/tests/unit/test_resultset.py +++ b/axelrod/tests/unit/test_resultset.py @@ -422,7 +422,7 @@ def test_self_interaction_for_random_strategies(self): axelrod.seed(0) players = [s() for s in axelrod.demo_strategies] tournament = axelrod.Tournament(players, repetitions=2, turns=5) - results = tournament.play() + results = tournament.play(progress_bar=False) self.assertEqual(results.payoff_diffs_means[-1][-1], 1.0) def test_equality(self): @@ -432,7 +432,7 @@ def test_equality(self): players = [s() for s in axelrod.demo_strategies] tournament = axelrod.Tournament(players, repetitions=2, turns=5) - results = tournament.play() + results = tournament.play(progress_bar=False) self.assertNotEqual(results, rs_sets[0]) def test_summarise(self): @@ -482,9 +482,9 @@ class TestResultSetFromFile(unittest.TestCase): axelrod.TitForTat(), axelrod.Defector()] tournament = axelrod.Tournament(players=players, turns=2, repetitions=3) - tournament.play(filename=filename) + tournament.play(filename=filename, progress_bar=False) - interactions = iu.read_interactions_from_file(filename) + interactions = iu.read_interactions_from_file(filename, progress_bar=False) def test_init(self): brs = axelrod.ResultSetFromFile(self.filename, progress_bar=False) @@ -495,7 +495,7 @@ def test_init(self): 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) + game=game) self.assertEqual(brs.game.RPST(), (-1, -1, -1, -1)) def test_init_with_progress_bar(self): @@ -507,7 +507,7 @@ def test_init_with_progress_bar(self): def test_init_with_num_interactions(self): """Just able to test that no error occurs""" - brs = axelrod.ResultSetFromFile(self.filename, progress_bar=True, + brs = axelrod.ResultSetFromFile(self.filename, progress_bar=False, num_interactions=18) self.assertEqual(brs.nplayers, len(self.players)) self.assertEqual(brs.repetitions, 3) @@ -515,7 +515,7 @@ def test_init_with_num_interactions(self): def test_init_with_players_repetitions(self): """Just able to test that no error occurs""" - brs = axelrod.ResultSetFromFile(self.filename, progress_bar=True, + 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)) @@ -545,7 +545,8 @@ def test_equality_with_round_robin(self, tournament): tournament.play(filename=filename, progress_bar=False, build_results=False) brs = axelrod.ResultSetFromFile(filename, progress_bar=False) - interactions = iu.read_interactions_from_file(filename) + interactions = iu.read_interactions_from_file(filename, + progress_bar=False) rs = axelrod.ResultSet(tournament.players, interactions, progress_bar=False) @@ -568,7 +569,8 @@ def test_equality_with_prob_end(self, tournament): tournament.play(filename=filename, progress_bar=False, build_results=False) brs = axelrod.ResultSetFromFile(filename, progress_bar=False) - interactions = iu.read_interactions_from_file(filename) + interactions = iu.read_interactions_from_file(filename, + progress_bar=False) rs = axelrod.ResultSet(tournament.players, interactions, progress_bar=False) diff --git a/axelrod/tests/unit/test_tournament.py b/axelrod/tests/unit/test_tournament.py index 3b4a042c7..e67de4c04 100644 --- a/axelrod/tests/unit/test_tournament.py +++ b/axelrod/tests/unit/test_tournament.py @@ -160,7 +160,7 @@ def test_no_progress_bar_play(self): results = tournament.play(progress_bar=False, build_results=False, filename=self.filename) self.assertIsNone(results) - results = axelrod.ResultSetFromFile(self.filename) + results = axelrod.ResultSetFromFile(self.filename, progress_bar=False) self.assertIsInstance(results, axelrod.ResultSet) self.assertRaises(AttributeError, call_progress_bar) @@ -419,7 +419,7 @@ def test_no_build_result_set(self): self.assertIsNone(results) # Checking that results were written properly - results = axelrod.ResultSetFromFile(self.filename) + results = axelrod.ResultSetFromFile(self.filename, progress_bar=False) self.assertIsInstance(results, axelrod.ResultSet) @given(turns=integers(min_value=1, max_value=200)) From 381388612ab92ff1281f0c6f454a389cd8019bb9 Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Sat, 15 Oct 2016 19:29:13 +0100 Subject: [PATCH 17/26] Address comments from Marc - Include underscore for all build methods. - Remove 0.0 from state distribution sum. - 'C'/'D' -> C/D - Single line for swap. - Probability count on one line. - Improve readability of summary measures. --- axelrod/result_set.py | 69 ++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/axelrod/result_set.py b/axelrod/result_set.py index 9ecaa43b6..11bdb0224 100644 --- a/axelrod/result_set.py +++ b/axelrod/result_set.py @@ -7,6 +7,9 @@ from . import eigen from .game import Game import axelrod.interaction_utils as iu +from axelrod import Actions + +C, D = Actions.C, Actions.D def update_progress_bar(method): @@ -142,7 +145,7 @@ def _build_players(self): return players @update_progress_bar - def build_eigenmoses_rating(self): + def _build_eigenmoses_rating(self): """ Returns: -------- @@ -156,7 +159,7 @@ def build_eigenmoses_rating(self): return eigenvector.tolist() @update_progress_bar - def build_eigenjesus_rating(self): + def _build_eigenjesus_rating(self): """ Returns: -------- @@ -170,7 +173,7 @@ def build_eigenjesus_rating(self): return eigenvector.tolist() @update_progress_bar - def build_cooperating_rating(self): + def _build_cooperating_rating(self): """ Returns: -------- @@ -201,7 +204,7 @@ def build_cooperating_rating(self): in zip(self.cooperation, lengths)] @update_progress_bar - def build_vengeful_cooperation(self): + def _build_vengeful_cooperation(self): """ Returns: -------- @@ -215,7 +218,7 @@ def build_vengeful_cooperation(self): for row in self.normalised_cooperation] @update_progress_bar - def build_payoff_diffs_means(self): + def _build_payoff_diffs_means(self): """ Returns: -------- @@ -237,7 +240,7 @@ def build_payoff_diffs_means(self): return payoff_diffs_means @update_progress_bar - def build_payoff_stddevs(self): + def _build_payoff_stddevs(self): """ Returns: -------- @@ -274,7 +277,7 @@ def build_payoff_stddevs(self): return payoff_stddevs @update_progress_bar - def build_payoff_matrix(self): + def _build_payoff_matrix(self): """ Returns: -------- @@ -309,7 +312,7 @@ def build_payoff_matrix(self): return payoff_matrix @update_progress_bar - def build_ranked_names(self): + def _build_ranked_names(self): """ Returns: -------- @@ -320,7 +323,7 @@ def build_ranked_names(self): return [str(self.players[i]) for i in self.ranking] @update_progress_bar - def build_ranking(self): + def _build_ranking(self): """ Returns: -------- @@ -336,7 +339,7 @@ def build_ranking(self): key=lambda i: -nanmedian(self.normalised_scores[i])) @update_progress_bar - def build_normalised_state_distribution(self): + def _build_normalised_state_distribution(self): """ Returns ---------- @@ -350,7 +353,7 @@ def build_normalised_state_distribution(self): for player in self.state_distribution: counters = [] for counter in player: - total = sum(counter.values(), 0.0) + total = sum(counter.values()) counters.append(Counter({key: value / total for key, value in counter.items()})) norm.append(counters) @@ -436,8 +439,7 @@ def _update_cooperation(self, p1, p2, cooperations): def _update_state_distribution(self, p1, p2, counter): self.state_distribution[p1][p2] += counter - counter[('C', 'D')], counter[('D', 'C')] = (counter[('D', 'C')], - counter[('C', 'D')]) + counter[(C, D)], counter[(D, C)] = counter[(D, C)], counter[(C, D)] self.state_distribution[p2][p1] += counter def _update_good_partner_matrix(self, p1, p2, cooperations): @@ -471,7 +473,7 @@ def _summarise_normalised_cooperation(self): pass @update_progress_bar - def build_good_partner_rating(self): + def _build_good_partner_rating(self): return [sum(self.good_partner_matrix[player]) / max(1, float(self.total_interactions[player])) for player in range(self.nplayers)] @@ -532,17 +534,17 @@ def _build_score_related_metrics(self, progress_bar=False, self._summarise_normalised_scores() self._summarise_normalised_cooperation() - self.ranking = self.build_ranking() - self.normalised_state_distribution = self.build_normalised_state_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.good_partner_rating = self.build_good_partner_rating() - self.eigenjesus_rating = self.build_eigenjesus_rating() - self.eigenmoses_rating = self.build_eigenmoses_rating() + self.ranking = self._build_ranking() + self.normalised_state_distribution = self._build_normalised_state_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.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() @@ -594,25 +596,26 @@ def summarise(self): "CC_rate", "CD_rate", "DC_rate", "DD_rate"]) - states = [('C', 'C'), ('C', 'D'), ('D', 'C'), ('D', 'D')] + states = [(C, C), (C, D), (D, C), (D, D)] state_prob = [] for i, player in enumerate(self.normalised_state_distribution): counts = [] for state in states: - counts.append(sum([opp[state] for j, opp in enumerate(player) - if i != j])) + p = sum([opp[state] for j, opp in enumerate(player) if i != j]) + counts.append(p) try: counts = [c / sum(counts) for c in counts] except ZeroDivisionError: counts = [0 for c in counts] state_prob.append(counts) - summary_data = list(zip(self.players, median_scores, - self.cooperating_rating, median_wins)) + summary_measures = list(zip(self.players, median_scores, + self.cooperating_rating, median_wins)) - summary_data = [self.player(rank, *(list(summary_data[i]) - + state_prob[i])) for - rank, i in enumerate(self.ranking)] + summary_data = [] + for rank, i in enumerate(self.ranking): + data = list(summary_measures[i]) + state_prob[i] + summary_data.append(self.player(rank, *data)) return summary_data From a95d610ecd1ff0013785ea4658616390ba3a28ea Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Sat, 15 Oct 2016 19:53:28 +0100 Subject: [PATCH 18/26] Include missing docstrings. --- axelrod/result_set.py | 146 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/axelrod/result_set.py b/axelrod/result_set.py index 11bdb0224..63c036065 100644 --- a/axelrod/result_set.py +++ b/axelrod/result_set.py @@ -395,25 +395,84 @@ def _build_empty_metrics(self, keep_interactions=False): 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) 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] @@ -422,33 +481,95 @@ def _update_wins(self, repetition, p1, p2, interaction): self.wins[winner_index][repetition] += 1 def _update_scores(self, repetition, p1, p2, interaction): + """ + During a read of the data, update the scores 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 + """ 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 def _update_normalised_scores(self, repetition, p1, p2, scores_per_turn): + """ + During a read of the data, update the normalised scores attribute + + Parameters + ---------- + + 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 + """ for index, player in enumerate([p1, p2]): score_per_turn = scores_per_turn[index] self.normalised_scores[player][repetition].append(score_per_turn) def _update_cooperation(self, p1, p2, cooperations): + """ + During a read of the data, update the cooperation attribute + + Parameters + ---------- + + p1, p2 : int + The indices of the first and second player + cooperations : tuple + A 2 tuple with the count of cooperation each player + """ self.cooperation[p1][p2] += cooperations[0] self.cooperation[p2][p1] += cooperations[1] def _update_state_distribution(self, p1, p2, counter): + """ + During a read of the data, update the state_distribution attribute + + Parameters + ---------- + + 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 counter[(C, D)], counter[(D, C)] = counter[(D, C)], counter[(C, D)] self.state_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 + + Parameters + ---------- + + p1, p2 : int + The indices of the first and second player + cooperations : tuple + A 2 tuple with the count of cooperation each player + """ if cooperations[0] >= cooperations[1]: self.good_partner_matrix[p1][p2] += 1 if cooperations[1] >= cooperations[0]: self.good_partner_matrix[p2][p1] += 1 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 != []: @@ -461,6 +582,9 @@ def _summarise_normalised_scores(self): pass def _summarise_normalised_cooperation(self): + """ + At the end of a read of the data, finalise the normalised cooperation + """ for i, rep in enumerate(self.normalised_cooperation): for j, cooperation in enumerate(rep): if cooperation != []: @@ -474,6 +598,10 @@ def _summarise_normalised_cooperation(self): @update_progress_bar def _build_good_partner_rating(self): + """ + At the end of a read of the data, build the good partner rating + attribute + """ return [sum(self.good_partner_matrix[player]) / max(1, float(self.total_interactions[player])) for player in range(self.nplayers)] @@ -550,6 +678,15 @@ def _build_score_related_metrics(self, progress_bar=False, self.progress_bar.close() def __eq__(self, other): + """ + Check equality of results set + + Parameters + ---------- + + other : axelrod.ResultSet + Another results set against which to check equality + """ return all([self.wins == other.wins, self.match_lengths == other.match_lengths, self.scores == other.scores, @@ -571,6 +708,15 @@ def __eq__(self, other): self.eigenjesus_rating == other.eigenjesus_rating]) def __ne__(self, other): + """ + Check inequality of results set + + Parameters + ---------- + + other : axelrod.ResultSet + Another results set against which to check inequality + """ return not self.__eq__(other) def summarise(self): From d597a1842b34b1d080f7b08dd1a67ce3524ecb34 Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Sat, 15 Oct 2016 20:03:30 +0100 Subject: [PATCH 19/26] Add a property test: check summary doesn't fail. --- axelrod/tests/unit/test_resultset.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/axelrod/tests/unit/test_resultset.py b/axelrod/tests/unit/test_resultset.py index f55073799..33a2e137e 100644 --- a/axelrod/tests/unit/test_resultset.py +++ b/axelrod/tests/unit/test_resultset.py @@ -1247,3 +1247,15 @@ def test_summarise(self): self.assertEqual(player.CD_rate, 0) self.assertEqual(player.DC_rate, 0) self.assertEqual(player.DD_rate, 0) + + +class TestSummary(unittest.TestCase): + """Separate test to check that summary always builds without failures""" + @given(tournament=tournaments(max_size=5, + max_turns=5, + max_repetitions=3)) + @settings(max_examples=50, timeout=0) + def test_summarise_without_failure(self, tournament): + results = tournament.play(progress_bar=False) + sd = results.summarise() + self.assertIsInstance(sd, list) From 289e4e77085c25c92c9fa8e0bd231e197ea5e5c5 Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Sat, 15 Oct 2016 20:10:48 +0100 Subject: [PATCH 20/26] Add description of states to docs. --- docs/tutorials/getting_started/tournament_results.rst | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/tutorials/getting_started/tournament_results.rst b/docs/tutorials/getting_started/tournament_results.rst index baff9d51b..50aa8e881 100644 --- a/docs/tutorials/getting_started/tournament_results.rst +++ b/docs/tutorials/getting_started/tournament_results.rst @@ -217,7 +217,10 @@ We see that :code:`Cooperator` for all the rounds (as expected):: State distribution counts ------------------------- -This gives a total state count against each opponent:: +This gives a total state count against each opponent. A state corresponds to 1 +turn of a match and can be one of :code:`('C', 'C'), ('C', 'D'), ('D', 'C'), +('D', 'D')` where the first element is the action of the player in question and +the second the action of the opponent:: >>> pprint.pprint(results.state_distribution) [[Counter(), @@ -240,7 +243,11 @@ This gives a total state count against each opponent:: Normalised state distribution ----------------------------- -This gives the average rate state distribution against each opponent:: +This gives the average rate state distribution against each opponent. +A state corresponds to 1 +turn of a match and can be one of :code:`('C', 'C'), ('C', 'D'), ('D', 'C'), +('D', 'D')` where the first element is the action of the player in question and +the second the action of the opponent:: >>> pprint.pprint(results.normalised_state_distribution) [[Counter(), From 199c639a2b0cecaddb68eb50b41784fd7873b3dd Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Sat, 15 Oct 2016 20:25:44 +0100 Subject: [PATCH 21/26] Float division and more expansive hypothesis test. --- axelrod/result_set.py | 6 +++--- axelrod/tests/unit/test_resultset.py | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/axelrod/result_set.py b/axelrod/result_set.py index 63c036065..27d9d0e0f 100644 --- a/axelrod/result_set.py +++ b/axelrod/result_set.py @@ -354,8 +354,8 @@ def _build_normalised_state_distribution(self): counters = [] for counter in player: total = sum(counter.values()) - counters.append(Counter({key: value / total for key, value in - counter.items()})) + counters.append(Counter({key: float(value) / total for + key, value in counter.items()})) norm.append(counters) return norm @@ -750,7 +750,7 @@ def summarise(self): p = sum([opp[state] for j, opp in enumerate(player) if i != j]) counts.append(p) try: - counts = [c / sum(counts) for c in counts] + counts = [float(c) / sum(counts) for c in counts] except ZeroDivisionError: counts = [0 for c in counts] state_prob.append(counts) diff --git a/axelrod/tests/unit/test_resultset.py b/axelrod/tests/unit/test_resultset.py index 33a2e137e..8812afe18 100644 --- a/axelrod/tests/unit/test_resultset.py +++ b/axelrod/tests/unit/test_resultset.py @@ -1259,3 +1259,9 @@ def test_summarise_without_failure(self, tournament): results = tournament.play(progress_bar=False) sd = results.summarise() self.assertIsInstance(sd, list) + + for player in sd: + # round for numerical error + total_rate = round(player.CC_rate + player.CD_rate + + player.DC_rate + player.DD_rate, 3) + self.assertTrue(total_rate in [0, 1]) From 988b849fb121fdd9aba0a77da23a5df913011459 Mon Sep 17 00:00:00 2001 From: margaret Date: Sun, 16 Oct 2016 05:33:39 +0200 Subject: [PATCH 22/26] Incorporate comments Set memory_depth to float('inf'). Remove redundant history length check. Remove redundant else statements. Remove Prober4.cooperation_pool and replace with history length check. Test Prober4.turned_defector value. Revert the update of strategies counter in doc tests. --- axelrod/strategies/prober.py | 42 ++++++++----------- axelrod/tests/unit/test_prober.py | 24 +++++------ .../advanced/classification_of_strategies.rst | 4 +- 3 files changed, 32 insertions(+), 38 deletions(-) diff --git a/axelrod/strategies/prober.py b/axelrod/strategies/prober.py index c4655aaf6..0914a1b67 100644 --- a/axelrod/strategies/prober.py +++ b/axelrod/strategies/prober.py @@ -118,7 +118,7 @@ class Prober4(Player): name = 'Prober 4' classifier = { 'stochastic': False, - 'memory_depth': 1, + 'memory_depth': float('inf'), 'makes_use_of': set(), 'long_run_time': False, 'inspects_source': False, @@ -134,39 +134,33 @@ def __init__(self): ] self.just_Ds = 0 self.unjust_Ds = 0 - self.cooperation_pool = [C] * 5 self.turned_defector = False def strategy(self, opponent): - if len(opponent.history) == 0: - return self.init_sequence[0] if len(self.history) == 0: return self.init_sequence[0] - else: - turn = len(self.history) - if turn < len(self.init_sequence): - if opponent.history[-1] == D: - if self.history[-1] == D: - self.just_Ds += 1 - if self.history[-1] == C: - self.unjust_Ds += 1 - return self.init_sequence[turn] - if turn == len(self.init_sequence): - diff_in_Ds = abs(self.just_Ds - self.unjust_Ds) - self.turned_defector = (diff_in_Ds <= 2) - if self.turned_defector: - return D - if not self.turned_defector: - if self.cooperation_pool: - return self.cooperation_pool.pop() - else: - return D if opponent.history[-1] == D else C + turn = len(self.history) + if turn < len(self.init_sequence): + if opponent.history[-1] == D: + if self.history[-1] == D: + self.just_Ds += 1 + if self.history[-1] == C: + self.unjust_Ds += 1 + return self.init_sequence[turn] + if turn == len(self.init_sequence): + diff_in_Ds = abs(self.just_Ds - self.unjust_Ds) + self.turned_defector = (diff_in_Ds <= 2) + if self.turned_defector: + return D + if not self.turned_defector: + if turn < len(self.init_sequence) + 5: + return C + return D if opponent.history[-1] == D else C def reset(self): Player.reset(self) self.just_Ds = 0 self.unjust_Ds = 0 - self.cooperation_pool = [C] * 5 self.turned_defector = False diff --git a/axelrod/tests/unit/test_prober.py b/axelrod/tests/unit/test_prober.py index 726ab3f4e..c53fde0b1 100644 --- a/axelrod/tests/unit/test_prober.py +++ b/axelrod/tests/unit/test_prober.py @@ -105,7 +105,7 @@ class TestProber4(TestPlayer): player = axelrod.Prober4 expected_classifier = { 'stochastic': False, - 'memory_depth': 1, + 'memory_depth': float('inf'), 'makes_use_of': set(), 'long_run_time': False, 'inspects_source': False, @@ -115,7 +115,6 @@ class TestProber4(TestPlayer): initial_sequence = [ C, C, D, C, D, D, D, C, C, D, C, D, C, C, D, C, D, D, C, D ] - cooperation_pool = [C] * 5 def test_initial_strategy(self): """Starts by playing CCDCDDDCCDCDCCDCDDCD.""" @@ -137,8 +136,9 @@ def test_strategy(self): history1 = self.initial_sequence responses = [D] * 10 + attrs = {'turned_defector': True} for history2 in provocative_histories: - self.responses_test(history1, history2, responses) + self.responses_test(history1, history2, responses, attrs=attrs) # Otherwise cooperates for 5 rounds unprovocative_histories = [ @@ -149,27 +149,27 @@ def test_strategy(self): [C, C, C, C, D, D, C, C, D, C, C, D, D, C, D, C, D, C, C, C], ] - history1 = self.initial_sequence - responses = self.cooperation_pool + responses = [C] * 5 + attrs = {'turned_defector': False} for history2 in unprovocative_histories: - self.responses_test(history1, history2, responses) + self.responses_test(history1, history2, responses, attrs=attrs) # and plays like TFT afterwards - history1 += self.cooperation_pool - history2 += self.cooperation_pool - self.responses_test(history1, history2, [C]) + history1 += responses + history2 += responses + self.responses_test(history1, history2, [C], attrs=attrs) history1 += [C] history2 += [D] - self.responses_test(history1, history2, [D]) + self.responses_test(history1, history2, [D], attrs=attrs) history1 += [D] history2 += [C] - self.responses_test(history1, history2, [C]) + self.responses_test(history1, history2, [C], attrs=attrs) history1 += [C] history2 += [D] - self.responses_test(history1, history2, [D]) + self.responses_test(history1, history2, [D], attrs=attrs) class TestHardProber(TestPlayer): diff --git a/docs/tutorials/advanced/classification_of_strategies.rst b/docs/tutorials/advanced/classification_of_strategies.rst index d6e478986..b863ef2e5 100644 --- a/docs/tutorials/advanced/classification_of_strategies.rst +++ b/docs/tutorials/advanced/classification_of_strategies.rst @@ -58,7 +58,7 @@ make a decision:: ... } >>> strategies = axl.filtered_strategies(filterset) >>> len(strategies) - 25 + 24 Multiple filters can be specified within the filterset dictionary. To specify a range of memory_depth values, we can use the 'min_memory_depth' and @@ -70,7 +70,7 @@ range of memory_depth values, we can use the 'min_memory_depth' and ... } >>> strategies = axl.filtered_strategies(filterset) >>> len(strategies) - 42 + 41 We can also identify strategies that make use of particular properties of the tournament. For example, here is the number of strategies that make use of the From 9f853a85eb9a7764b9cfb1e296a6246148980aec Mon Sep 17 00:00:00 2001 From: Nikoleta Glynatsi Date: Sat, 15 Oct 2016 20:43:06 +0100 Subject: [PATCH 23/26] Documentation for Axelrod's first and second tournament. --- axelrod/strategies/axelrod_first.py | 78 +++++++++++++++++++++------- axelrod/strategies/axelrod_second.py | 19 ++++++- docs/reference/bibliography.rst | 1 + 3 files changed, 76 insertions(+), 22 deletions(-) diff --git a/axelrod/strategies/axelrod_first.py b/axelrod/strategies/axelrod_first.py index 97064e2e9..0fb85be9c 100644 --- a/axelrod/strategies/axelrod_first.py +++ b/axelrod/strategies/axelrod_first.py @@ -12,8 +12,16 @@ class Davis(Player): - """A player starts by cooperating for 10 rounds then plays Grudger, - defecting if at any point the opponent has defected.""" + """ + Submitted to Axelrod's first tournament by Morton Davis. + + A player starts by cooperating for 10 rounds then plays Grudger, + defecting if at any point the opponent has defected. + + Names: + + - Davis: [Axelrod1980]_ + """ name = 'Davis' classifier = { @@ -49,8 +57,11 @@ def strategy(self, opponent): class RevisedDowning(Player): """Revised Downing attempts to determine if players are cooperative or not. - If so, it cooperates with them. This strategy would have won Axelrod's first - tournament. + If so, it cooperates with them. This strategy would have won Axelrod's first tournament. + + Names: + + - Revised Downing: [Axelrod1980]_ """ name = "Revised Downing" @@ -127,8 +138,14 @@ def reset(self): class Feld(Player): """ + Submitted to Axelrod's first tournament by Scott Feld. + Defects when opponent defects. Cooperates with a probability that decreases to 0.5 at round 200. + + Names: + + - Feld: [Axelrod1980]_ """ name = "Feld" @@ -182,9 +199,15 @@ def strategy(self, opponent): class Grofman(Player): """ + Submitted to Axelrod's first tournament by Bernard Grofman. + Cooperate on the first 2 moves. Return opponent's move for the next 5. Then cooperate if the last round's moves were the same, otherwise cooperate with probability 2/7. + + Names: + + - Grofman: [Axelrod1980]_ """ name = "Grofman" @@ -212,6 +235,8 @@ def strategy(self, opponent): class Joss(MemoryOnePlayer): """ + Submitted to Axelrod's first tournament by Johann Joss. + Cooperates with probability 0.9 when the opponent cooperates, otherwise emulates Tit-For-Tat. @@ -242,21 +267,18 @@ def __repr__(self): class Nydegger(Player): """ - The program begins with tit for tat for the first three moves, except that - if it was the only one to cooperate on the first move and the only one to - defect on the second move, it defects on the third move. After the third move, - its choice is determined from the 3 preceding outcomes in the following manner. - Let A be the sum formed by counting the other's defection as 2 points and one's - own as 1 point, and giving weights of 16, 4, and 1 to the preceding three - moves in chronological order. The choice can be described as defecting only - when A equals 1, 6, 7, 17, 22, 23, 26, 29, 30, 31, 33, 38, 39, 45, 49, 54, - 55, 58, or 61. Thus if all three preceding moves are mutual defection, - A = 63 and the rule cooperates. This rule was designed for use in laboratory - experiments as a stooge which had a memory and appeared to be trustworthy, - potentially cooperative, but not gullible. - - -- Axelrod, "Effective Choice in the Prisoner's Dilemma" + Submitted to Axelrod's first tournament by Rudy Nydegger. + + The program begins with tit for tat for the first three moves, except + that if it was the only one to cooperate on the first move and the only one to defect on the second move, it defects on the third move. After the third move, its choice is determined from the 3 preceding outcomes in the following manner. + + Let A be the sum formed by counting the other's defection as 2 points and one's own as 1 point, and giving weights of 16, 4, and 1 to the preceding three moves in chronological order. The choice can be described as defecting only when A equals 1, 6, 7, 17, 22, 23, 26, 29, 30, 31, 33, 38, 39, 45, 49, 54, 55, 58, or 61. + Thus if all three preceding moves are mutual defection, A = 63 and the rule cooperates. This rule was designed for use in laboratory experiments as a stooge which had a memory and appeared to be trustworthy, potentially cooperative, but not gullible. + + Names: + + - Nydegger: [Axelrod1980]_ """ name = "Nydegger" @@ -309,9 +331,15 @@ def strategy(self, opponent): class Shubik(Player): """ + Submitted to Axelrod's first tournament by Martin Shubik. + Plays like Tit-For-Tat with the following modification. After each retaliation, the number of rounds that Shubik retaliates increases by 1. + + Names: + + - Shubik: [Axelrod1980]_ """ name = 'Shubik' @@ -371,8 +399,15 @@ def reset(self): class Tullock(Player): """ + Submitted to Axelrod's first tournament by Gordon Tulloc. + Cooperates for the first 11 rounds then randomly cooperates 10% less often - than the opponent has in previous rounds.""" + than the opponent has in previous rounds. + + Names: + + - Tullock: [Axelrod1980]_ + """ name = "Tullock" classifier = { @@ -423,7 +458,10 @@ class UnnamedStrategy(Player): score than the other player. Unfortunately, the complex process of adjustment frequently left the probability of cooperation in the 30% to 70% range, and therefore the rule appeared random to many other players. - -- Axelrod, "Effective Choice in the Prisoner's Dilemma" + + Names: + + - Unnamed Strategy: [Axelrod1980]_ Warning: This strategy is not identical to the original strategy (source unavailable) and was written based on published descriptions. diff --git a/axelrod/strategies/axelrod_second.py b/axelrod/strategies/axelrod_second.py index 8350369f6..90d1c668c 100644 --- a/axelrod/strategies/axelrod_second.py +++ b/axelrod/strategies/axelrod_second.py @@ -12,6 +12,12 @@ class Champion(Player): """ Strategy submitted to Axelrod's second tournament by Danny Champion. + + This player cooperates on the first 10 moves and plays Tit for Tat for the next 15 more moves. After 25 moves, the program cooperates unless all the following are true: the other player defected on the previous move, the other player cooperated less than 60% and the random number between 0 and 1 is greater that the other player's cooperation rate. + + Names: + + - Champion: [Axelrod1980b]_ """ name = "Champion" @@ -48,6 +54,12 @@ def strategy(self, opponent): class Eatherley(Player): """ Strategy submitted to Axelrod's second tournament by Graham Eatherley. + + A player that keeps track of how many times in the game the other player defected. After the other player defects, it defects with a probability equal to the ratio of the other's total defections to the total moves to that point. + + Names: + + - Eatherley: [Axelrod1980b]_ """ name = "Eatherley" @@ -79,8 +91,11 @@ class Tester(Player): """ Submitted to Axelrod's second tournament by David Gladstein. - Defects on the first move and plays TFT if the opponent ever defects (after - one apology cooperation round). Otherwise alternate cooperation and defection. + Defects on the first move and plays Tit For Tat if the opponent ever defects (after one apology cooperation round). Otherwise alternate cooperation and defection. + + Names: + + - Tester: [Axelrod1980b]_ """ name = "Tester" diff --git a/docs/reference/bibliography.rst b/docs/reference/bibliography.rst index 0ee9e3e58..55eb55cff 100644 --- a/docs/reference/bibliography.rst +++ b/docs/reference/bibliography.rst @@ -7,6 +7,7 @@ This is a collection of various bibliographic items referenced in the documentation. .. [Axelrod1980] Axelrod, R. (1980). Effective Choice in the Prisoner’s Dilemma. Journal of Conflict Resolution, 24(1), 3–25. +.. [Axelrod1980b] Axelrod, R. (1980). More Effective Choice in the Prisoner’s Dilemma. Journal of Conflict Resolution, 24(3), 379-403. .. [Axelrod1984] The Evolution of Cooperation. Basic Books. ISBN 0-465-02121-2. .. [Axelrod1995] Wu, J. and Axelrod, R. (1995). How to cope with noise in the Iterated prisoner’s dilemma, Journal of Conflict Resolution, 39(1), pp. 183–189. doi: 10.1177/0022002795039001008. .. [Banks1980] Banks, J. S., & Sundaram, R. K. (1990). Repeated games, finite automata, and complexity. Games and Economic Behavior, 2(2), 97–117. http://doi.org/10.1016/0899-8256(90)90024-O From a29870b3bd61ed853c24ecde74d5c5a52b80cd61 Mon Sep 17 00:00:00 2001 From: Nikoleta Glynatsi Date: Sun, 16 Oct 2016 11:46:50 +0100 Subject: [PATCH 24/26] Syntax corections --- axelrod/strategies/axelrod_first.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axelrod/strategies/axelrod_first.py b/axelrod/strategies/axelrod_first.py index 0fb85be9c..3c909302f 100644 --- a/axelrod/strategies/axelrod_first.py +++ b/axelrod/strategies/axelrod_first.py @@ -399,7 +399,7 @@ def reset(self): class Tullock(Player): """ - Submitted to Axelrod's first tournament by Gordon Tulloc. + Submitted to Axelrod's first tournament by Gordon Tullock. Cooperates for the first 11 rounds then randomly cooperates 10% less often than the opponent has in previous rounds. From b3eb0eaef612c6c8703f590d56e61c65c1167ef6 Mon Sep 17 00:00:00 2001 From: margaret Date: Sun, 16 Oct 2016 14:54:01 +0200 Subject: [PATCH 25/26] Incorporate comment - simplify if statement --- axelrod/strategies/prober.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/axelrod/strategies/prober.py b/axelrod/strategies/prober.py index 0914a1b67..ccc2df3c8 100644 --- a/axelrod/strategies/prober.py +++ b/axelrod/strategies/prober.py @@ -137,7 +137,7 @@ def __init__(self): self.turned_defector = False def strategy(self, opponent): - if len(self.history) == 0: + if not self.history: return self.init_sequence[0] turn = len(self.history) if turn < len(self.init_sequence): From 9a3e8ea49aacb3178477d8aeff4a76972d53ab95 Mon Sep 17 00:00:00 2001 From: Vince Knight Date: Sun, 16 Oct 2016 19:05:16 +0100 Subject: [PATCH 26/26] Release v1.13.0. --- CHANGES.md | 34 ++++++++++++++++++++++++++++++++++ axelrod/version.py | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 62dc736e6..46672efcf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,37 @@ +# v1.13.0, 2016-10-16 + +New strategy, state distribution and documentation + +- Adding Prober4 strategy + https://github.com/Axelrod-Python/Axelrod/pull/743 +- Adding state distribution to results set + https://github.com/Axelrod-Python/Axelrod/pull/742 +- More references for strategies + https://github.com/Axelrod-Python/Axelrod/pull/745 + +Here are all the commits for this PR: +https://github.com/Axelrod-Python/Axelrod/compare/v1.12.0...v1.13.0 + +# v1.12.0, 2016-10-13 + +Human interactive player, new strategy, under the hood improvements and +documentation. + +- You can play against an instance of `axelrod.Human` + https://github.com/Axelrod-Python/Axelrod/pull/732 +- Improved efficiency of result set from memory + https://github.com/Axelrod-Python/Axelrod/pull/737 +- Documentation improvements + https://github.com/Axelrod-Python/Axelrod/pull/741 + https://github.com/Axelrod-Python/Axelrod/pull/736 + https://github.com/Axelrod-Python/Axelrod/pull/735 + https://github.com/Axelrod-Python/Axelrod/pull/727 +- New strategy CyclerCCCDCD: + https://github.com/Axelrod-Python/Axelrod/pull/379 + +Here are all the commits for this PR: +https://github.com/Axelrod-Python/Axelrod/compare/v1.11.0...v1.12.0 + # v1.12.0, 2016-10-13 Human interactive player, new strategy, under the hood improvements and diff --git a/axelrod/version.py b/axelrod/version.py index b518f6eed..9a34ccc9f 100644 --- a/axelrod/version.py +++ b/axelrod/version.py @@ -1 +1 @@ -__version__ = "1.12.0" +__version__ = "1.13.0"