From 3bae2665855c1d4ce21d759bcee21853f7dd3e03 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sat, 27 Oct 2018 15:33:56 -0700 Subject: [PATCH 1/4] Initial implementatino of Adaptor --- axelrod/game.py | 2 +- axelrod/strategies/_strategies.py | 3 + axelrod/strategies/adaptor.py | 100 +++++++++++++++++++++ axelrod/tests/strategies/test_adaptor.py | 64 +++++++++++++ axelrod/tests/strategies/test_memoryone.py | 3 +- docs/reference/bibliography.rst | 1 + 6 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 axelrod/strategies/adaptor.py create mode 100644 axelrod/tests/strategies/test_adaptor.py diff --git a/axelrod/game.py b/axelrod/game.py index 27caac16f..1c3278275 100644 --- a/axelrod/game.py +++ b/axelrod/game.py @@ -38,7 +38,7 @@ def RPST(self) -> Tuple[Score, Score, Score, Score]: P = self.scores[(D, D)][0] S = self.scores[(C, D)][0] T = self.scores[(D, C)][0] - return (R, P, S, T) + return R, P, S, T def score(self, pair: Tuple[Action, Action]) -> Tuple[Score, Score]: """Returns the appropriate score for a decision pair. diff --git a/axelrod/strategies/_strategies.py b/axelrod/strategies/_strategies.py index 2f5a400e5..9297e2f5a 100644 --- a/axelrod/strategies/_strategies.py +++ b/axelrod/strategies/_strategies.py @@ -1,4 +1,5 @@ from .adaptive import Adaptive +from .adaptor import AdaptorBrief, AdaptorLong from .alternator import Alternator from .ann import EvolvedANN, EvolvedANN5, EvolvedANNNoise05 from .apavlov import APavlov2006, APavlov2011 @@ -230,6 +231,8 @@ all_strategies = [ Adaptive, AdaptiveTitForTat, + AdaptorBrief, + AdaptorLong, Aggravater, Alexei, ALLCorALLD, diff --git a/axelrod/strategies/adaptor.py b/axelrod/strategies/adaptor.py new file mode 100644 index 000000000..763cd1b94 --- /dev/null +++ b/axelrod/strategies/adaptor.py @@ -0,0 +1,100 @@ +from typing import Dict, Tuple + +from axelrod.action import Action +from axelrod.player import Player +from axelrod.random_ import random_choice + +from numpy import heaviside + +C, D = Action.C, Action.D + + +class AbstractAdaptor(Player): + """ + An adaptive strategy that updates an internal state based on the last + round of play. Using this state the player Cooperates with a probability + derived from the state. + + Names: + + - Adaptor: [Hauert2002]_ + + """ + + name = "AbstractAdaptor" + classifier = { + "memory_depth": float("inf"), # Long memory + "stochastic": True, + "makes_use_of": set(), + "long_run_time": False, + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + def __init__(self, d: Dict[Tuple[Action], float], perr: float = 0.01) -> None: + super().__init__() + self.perr = perr + if not d: + d = {(C, C): 1., # R + (C, D): 1., # S + (D, C): 1., # T + (D, D): 1. # P + } + self.d = d + self.s = 0. + + def strategy(self, opponent: Player) -> Action: + if self.history: + # Update internal state from the last play + last_round = (self.history[-1], opponent.history[-1]) + self.s += d[last_round] + + # Compute probability of Cooperation + p = self.perr + (1.0 - 2 * self.perr) * ( + heaviside(self.s + 1, 1) - heaviside(self.s - 1, 1)) + # Draw action + action = random_choice(p) + return action + + +class AdaptorBrief(Player): + """ + An Adaptor trained on short interactions. + + Names: + + - AdaptorBrief: [Hauert2002]_ + + """ + + name = "AdaptorBrief" + + def __init__(self) -> None: + d = {(C, C): 0., # R + (C, D): 1.001505, # S + (D, C): 0.992107, # T + (D, D): 0.638734 # P + } + super().__init__(d=d) + + +class AdaptorLong(Player): + """ + An Adaptor trained on long interactions. + + Names: + + - AdaptorLong: [Hauert2002]_ + + """ + + name = "AdaptorLong" + + def __init__(self) -> None: + d = {(C, C): 0., # R + (C, D): 1.888159, # S + (D, C): 1.858883, # T + (D, D): 0.995703 # P + } + super().__init__(d=d) diff --git a/axelrod/tests/strategies/test_adaptor.py b/axelrod/tests/strategies/test_adaptor.py new file mode 100644 index 000000000..1acee3a84 --- /dev/null +++ b/axelrod/tests/strategies/test_adaptor.py @@ -0,0 +1,64 @@ +"""Tests for the adaptor""" + +import unittest + +import axelrod +from axelrod import Game + +from .test_player import TestPlayer, test_four_vector + +C, D = axelrod.Action.C, axelrod.Action.D + + +class TestAdaptorBrief(TestPlayer): + + name = "AdaptorBrief" + player = axelrod.AdaptorBrief + expected_classifier = { + "memory_depth": 1, + "stochastic": True, + "makes_use_of": set(), + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + def test_strategy(self): + # No error. + actions = [(C, C), (C, C), (C, C), (C, C)] + self.versus_test( + opponent=axelrod.AdaptorBrief(), expected_actions=actions, seed=0 + ) + + # Error corrected. + actions = [(C, C), (D, C), (C, D), (C, C)] + self.versus_test( + opponent=axelrod.AdaptorBrief(), expected_actions=actions, seed=1 + ) + + +class TestAdaptorLong(TestPlayer): + + name = "AdaptorLong" + player = axelrod.AdaptorLong + expected_classifier = { + "memory_depth": 1, + "stochastic": True, + "makes_use_of": set(), + "inspects_source": False, + "manipulates_source": False, + "manipulates_state": False, + } + + def test_strategy(self): + # No error. + actions = [(C, C), (C, C), (C, C), (C, C)] + self.versus_test( + opponent=axelrod.AdaptorLong(), expected_actions=actions, seed=0 + ) + + # Error corrected. + actions = [(C, C), (D, C), (D, D), (D, D), (C, C)] + self.versus_test( + opponent=axelrod.AdaptorLong(), expected_actions=actions, seed=1 + ) diff --git a/axelrod/tests/strategies/test_memoryone.py b/axelrod/tests/strategies/test_memoryone.py index 8c2472750..e05c0ffa2 100644 --- a/axelrod/tests/strategies/test_memoryone.py +++ b/axelrod/tests/strategies/test_memoryone.py @@ -71,7 +71,8 @@ class TestWinShiftLoseStayTestPlayer(TestPlayer): def test_strategy(self): # Check that switches if does not get best payoff. actions = [(D, C), (C, D), (C, C), (D, D), (D, C)] - self.versus_test(opponent=axelrod.Alternator(), expected_actions=actions) + self.versus_test(opponent=axelrod.Alternator(), + expected_actions=actions) class TestGTFT(TestPlayer): diff --git a/docs/reference/bibliography.rst b/docs/reference/bibliography.rst index 2dd38cce1..dfc44d6e7 100644 --- a/docs/reference/bibliography.rst +++ b/docs/reference/bibliography.rst @@ -27,6 +27,7 @@ documentation. .. [Berg2015] Berg, P. Van Den, & Weissing, F. J. (2015). The importance of mechanisms for the evolution of cooperation. Proceedings of the Royal Society B-Biological Sciences, 282. .. [Eckhart2015] Eckhart Arnold (2016) CoopSim v0.9.9 beta 6. /~https://github.com/jecki/CoopSim/ .. [Frean1994] Frean, Marcus R. "The Prisoner's Dilemma without Synchrony." Proceedings: Biological Sciences, vol. 257, no. 1348, 1994, pp. 75–79. www.jstor.org/stable/50253. +.. [Hauert2002] Hauert, Christoph, and Olaf Stenull. "Simple adaptive strategy wins the prisoner's dilemma." Journal of Theoretical Biology 218.3 (2002): 261-272. .. [Hilbe2013] Hilbe, C., Nowak, M.A. and Traulsen, A. (2013). Adaptive dynamics of extortion and compliance, PLoS ONE, 8(11), p. e77886. doi: 10.1371/journal.pone.0077886. .. [Hilbe2017] Hilbe, C., Martinez-Vaquero, L. A., Chatterjee K., Nowak M. A. (2017). Memory-n strategies of direct reciprocity, Proceedings of the National Academy of Sciences May 2017, 114 (18) 4715-4720; doi: 10.1073/pnas.1621239114. .. [Kuhn2017] Kuhn, Steven, "Prisoner's Dilemma", The Stanford Encyclopedia of Philosophy (Spring 2017 Edition), Edward N. Zalta (ed.), https://plato.stanford.edu/archives/spr2017/entries/prisoner-dilemma/ From 6b83b04497211504bcd2d26244661d4c2470757e Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Sun, 28 Oct 2018 21:01:08 -0700 Subject: [PATCH 2/4] Update tests for Adaptor, mostly --- axelrod/strategies/adaptor.py | 27 ++++++------ axelrod/strategies/bush_mosteller.py | 8 ++-- axelrod/tests/strategies/test_adaptor.py | 42 ++++++++++++++++--- axelrod/tests/strategies/test_meta.py | 2 +- docs/reference/all_strategies.rst | 2 + .../advanced/classification_of_strategies.rst | 2 +- 6 files changed, 58 insertions(+), 25 deletions(-) diff --git a/axelrod/strategies/adaptor.py b/axelrod/strategies/adaptor.py index 763cd1b94..4a30cc1bf 100644 --- a/axelrod/strategies/adaptor.py +++ b/axelrod/strategies/adaptor.py @@ -32,14 +32,15 @@ class AbstractAdaptor(Player): "manipulates_state": False, } - def __init__(self, d: Dict[Tuple[Action], float], perr: float = 0.01) -> None: + def __init__(self, d: Dict[Tuple[Action, Action], float], + perr: float = 0.01) -> None: super().__init__() self.perr = perr if not d: - d = {(C, C): 1., # R - (C, D): 1., # S - (D, C): 1., # T - (D, D): 1. # P + d = {(C, C): 1., # R + (C, D): 1., # S + (D, C): 1., # T + (D, D): 1. # P } self.d = d self.s = 0. @@ -48,7 +49,7 @@ def strategy(self, opponent: Player) -> Action: if self.history: # Update internal state from the last play last_round = (self.history[-1], opponent.history[-1]) - self.s += d[last_round] + self.s += self.d[last_round] # Compute probability of Cooperation p = self.perr + (1.0 - 2 * self.perr) * ( @@ -58,7 +59,7 @@ def strategy(self, opponent: Player) -> Action: return action -class AdaptorBrief(Player): +class AdaptorBrief(AbstractAdaptor): """ An Adaptor trained on short interactions. @@ -71,15 +72,15 @@ class AdaptorBrief(Player): name = "AdaptorBrief" def __init__(self) -> None: - d = {(C, C): 0., # R - (C, D): 1.001505, # S - (D, C): 0.992107, # T - (D, D): 0.638734 # P + d = {(C, C): 0., # R + (C, D): -1.001505, # S + (D, C): 0.992107, # T + (D, D): -0.638734 # P } super().__init__(d=d) -class AdaptorLong(Player): +class AdaptorLong(AbstractAdaptor): """ An Adaptor trained on long interactions. @@ -95,6 +96,6 @@ def __init__(self) -> None: d = {(C, C): 0., # R (C, D): 1.888159, # S (D, C): 1.858883, # T - (D, D): 0.995703 # P + (D, D): -0.995703 # P } super().__init__(d=d) diff --git a/axelrod/strategies/bush_mosteller.py b/axelrod/strategies/bush_mosteller.py index 98a26f5a0..d6ed5adf3 100644 --- a/axelrod/strategies/bush_mosteller.py +++ b/axelrod/strategies/bush_mosteller.py @@ -51,15 +51,15 @@ def __init__( aspiration_level_divider: float, 3.0 Value that regulates the aspiration level, isn't modified during match - learning rate [0 , 1] - Percentage of learning speed + learning rate [0 , 1] + Percentage of learning speed Variables / Constants - _stimulus (Var: [-1 , 1]): float + stimulus (Var: [-1 , 1]): float Value that impacts the changes of action probability _aspiration_level: float Value that impacts the stimulus changes, isn't modified during match _init_c_prob , _init_d_prob : float - Values used to properly set up reset(), + Values used to properly set up reset(), set to original probabilities """ super().__init__() diff --git a/axelrod/tests/strategies/test_adaptor.py b/axelrod/tests/strategies/test_adaptor.py index 1acee3a84..e489bdd33 100644 --- a/axelrod/tests/strategies/test_adaptor.py +++ b/axelrod/tests/strategies/test_adaptor.py @@ -15,7 +15,7 @@ class TestAdaptorBrief(TestPlayer): name = "AdaptorBrief" player = axelrod.AdaptorBrief expected_classifier = { - "memory_depth": 1, + "memory_depth": float("inf"), "stochastic": True, "makes_use_of": set(), "inspects_source": False, @@ -31,9 +31,27 @@ def test_strategy(self): ) # Error corrected. - actions = [(C, C), (D, C), (C, D), (C, C)] + actions = [(C, C), (C, D), (D, C), (C, C)] self.versus_test( - opponent=axelrod.AdaptorBrief(), expected_actions=actions, seed=1 + opponent=axelrod.AdaptorBrief(), expected_actions=actions, seed=22 + ) + + # Error corrected, example 2 + actions = [(D, C), (C, D), (D, C), (C, D), (C, C)] + self.versus_test( + opponent=axelrod.AdaptorBrief(), expected_actions=actions, seed=925 + ) + + # Versus Cooperator + actions = [(C, C)] * 8 + self.versus_test( + opponent=axelrod.Cooperator(), expected_actions=actions, seed=0 + ) + + # Versus Defector + actions = [(C, D), (D, D), (D, D), (D, D), (D, D), (D, D), (D, D)] + self.versus_test( + opponent=axelrod.Defector(), expected_actions=actions, seed=0 ) @@ -42,7 +60,7 @@ class TestAdaptorLong(TestPlayer): name = "AdaptorLong" player = axelrod.AdaptorLong expected_classifier = { - "memory_depth": 1, + "memory_depth": float("inf"), "stochastic": True, "makes_use_of": set(), "inspects_source": False, @@ -58,7 +76,19 @@ def test_strategy(self): ) # Error corrected. - actions = [(C, C), (D, C), (D, D), (D, D), (C, C)] + actions = [(C, C), (C, D), (D, D), (C, C), (C, C)] + self.versus_test( + opponent=axelrod.AdaptorLong(), expected_actions=actions, seed=22 + ) + + # Versus Cooperator + actions = [(C, C)] * 8 + self.versus_test( + opponent=axelrod.Cooperator(), expected_actions=actions, seed=0 + ) + + # Versus Defector + actions = [(C, D), (D, D), (C, D), (D, D), (D, D), (C, D), (D, D)] self.versus_test( - opponent=axelrod.AdaptorLong(), expected_actions=actions, seed=1 + opponent=axelrod.Defector(), expected_actions=actions, seed=0 ) diff --git a/axelrod/tests/strategies/test_meta.py b/axelrod/tests/strategies/test_meta.py index 49fe4acd9..db0593055 100644 --- a/axelrod/tests/strategies/test_meta.py +++ b/axelrod/tests/strategies/test_meta.py @@ -548,7 +548,7 @@ class TestNMWEStochastic(TestMetaPlayer): } def test_strategy(self): - actions = [(C, C), (C, D), (D, C), (D, D), (D, C)] + actions = [(C, C), (C, D), (C, C), (D, D), (D, C)] self.versus_test(opponent=axelrod.Alternator(), expected_actions=actions) diff --git a/docs/reference/all_strategies.rst b/docs/reference/all_strategies.rst index 6cf318d00..9e429f562 100644 --- a/docs/reference/all_strategies.rst +++ b/docs/reference/all_strategies.rst @@ -8,6 +8,8 @@ Here are the docstrings of all the strategies in the library. .. automodule:: axelrod.strategies.adaptive :members: +.. automodule:: axelrod.strategies.adaptor + :members: .. automodule:: axelrod.strategies.alternator :members: .. automodule:: axelrod.strategies.ann diff --git a/docs/tutorials/advanced/classification_of_strategies.rst b/docs/tutorials/advanced/classification_of_strategies.rst index 8e2daf064..cd45f46b6 100644 --- a/docs/tutorials/advanced/classification_of_strategies.rst +++ b/docs/tutorials/advanced/classification_of_strategies.rst @@ -47,7 +47,7 @@ strategies:: ... } >>> strategies = axl.filtered_strategies(filterset) >>> len(strategies) - 82 + 84 Or, to find out how many strategies only use 1 turn worth of memory to make a decision:: From a9dfaba33c0eb06e703fb114d44242b996e73f26 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Mon, 29 Oct 2018 19:03:26 -0700 Subject: [PATCH 3/4] Remove default value of delta dictionary in AbstractAdaptor --- axelrod/strategies/adaptor.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/axelrod/strategies/adaptor.py b/axelrod/strategies/adaptor.py index 4a30cc1bf..0f564a7c7 100644 --- a/axelrod/strategies/adaptor.py +++ b/axelrod/strategies/adaptor.py @@ -36,12 +36,6 @@ def __init__(self, d: Dict[Tuple[Action, Action], float], perr: float = 0.01) -> None: super().__init__() self.perr = perr - if not d: - d = {(C, C): 1., # R - (C, D): 1., # S - (D, C): 1., # T - (D, D): 1. # P - } self.d = d self.s = 0. From ae51ba486c26e3e96dd27f29c6f2c5d96eb61049 Mon Sep 17 00:00:00 2001 From: Marc Harper Date: Tue, 30 Oct 2018 06:59:36 -0700 Subject: [PATCH 4/4] Update variable names for Adaptor and update docstring --- axelrod/strategies/adaptor.py | 39 +++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/axelrod/strategies/adaptor.py b/axelrod/strategies/adaptor.py index 0f564a7c7..2648b2704 100644 --- a/axelrod/strategies/adaptor.py +++ b/axelrod/strategies/adaptor.py @@ -15,6 +15,13 @@ class AbstractAdaptor(Player): round of play. Using this state the player Cooperates with a probability derived from the state. + s, float: + the internal state, initially 0 + perr, float: + an error threshold for misinterpreted moves + delta, a dictionary of floats: + additive update values for s depending on the last round's outcome + Names: - Adaptor: [Hauert2002]_ @@ -32,18 +39,18 @@ class AbstractAdaptor(Player): "manipulates_state": False, } - def __init__(self, d: Dict[Tuple[Action, Action], float], + def __init__(self, delta: Dict[Tuple[Action, Action], float], perr: float = 0.01) -> None: super().__init__() self.perr = perr - self.d = d + self.delta = delta self.s = 0. def strategy(self, opponent: Player) -> Action: if self.history: # Update internal state from the last play last_round = (self.history[-1], opponent.history[-1]) - self.s += self.d[last_round] + self.s += self.delta[last_round] # Compute probability of Cooperation p = self.perr + (1.0 - 2 * self.perr) * ( @@ -66,12 +73,13 @@ class AdaptorBrief(AbstractAdaptor): name = "AdaptorBrief" def __init__(self) -> None: - d = {(C, C): 0., # R - (C, D): -1.001505, # S - (D, C): 0.992107, # T - (D, D): -0.638734 # P - } - super().__init__(d=d) + delta = { + (C, C): 0., # R + (C, D): -1.001505, # S + (D, C): 0.992107, # T + (D, D): -0.638734 # P + } + super().__init__(delta=delta) class AdaptorLong(AbstractAdaptor): @@ -87,9 +95,10 @@ class AdaptorLong(AbstractAdaptor): name = "AdaptorLong" def __init__(self) -> None: - d = {(C, C): 0., # R - (C, D): 1.888159, # S - (D, C): 1.858883, # T - (D, D): -0.995703 # P - } - super().__init__(d=d) + delta = { + (C, C): 0., # R + (C, D): 1.888159, # S + (D, C): 1.858883, # T + (D, D): -0.995703 # P + } + super().__init__(delta=delta)