Skip to content

Commit 8fa56d2

Browse files
authored
This PR adds the Momentum strategy (#1469)
* Add Momentum strategy
1 parent 6d2d465 commit 8fa56d2

12 files changed

+164
-8
lines changed

axelrod/classifier.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ def __getitem__(
185185
raise KeyError("Unknown classifier")
186186

187187
def classify_player_for_this_classifier(
188-
player: Union[Player, Type[Player]]
188+
player: Union[Player, Type[Player]],
189189
) -> Any:
190190
def try_lookup() -> Any:
191191
try:

axelrod/makes_use_of.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def makes_use_of(player: Type[Player]) -> Set[Text]:
3636

3737

3838
def makes_use_of_variant(
39-
player_or_method: Union[Callable, Type[Player]]
39+
player_or_method: Union[Callable, Type[Player]],
4040
) -> Set[Text]:
4141
"""A version of makes_use_of that works on functions or player classes."""
4242
try:

axelrod/strategies/_strategies.py

+2
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@
193193
from .memorytwo import AON2, MEM2, DelayedAON1
194194
from .memorytwo import MemoryTwoPlayer # pylint: disable=unused-import
195195

196+
from .momentum import Momentum
196197
from .mutual import Desperate, Hopeless, Willing
197198
from .negation import Negation
198199
from .oncebitten import FoolMeOnce, ForgetfulFoolMeOnce, OnceBitten
@@ -402,6 +403,7 @@
402403
MEM2,
403404
MathConstantHunter,
404405
Michaelos,
406+
Momentum,
405407
NTitsForMTats,
406408
NaiveProber,
407409
Negation,

axelrod/strategies/frequency_analyzer.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def __init__(self) -> None:
6262
"""
6363
super().__init__()
6464
self.minimum_cooperation_ratio = 0.25
65-
self.frequency_table = dict()
65+
self.frequency_table: dict = dict()
6666
self.last_sequence = ""
6767
self.current_sequence = ""
6868

axelrod/strategies/memorytwo.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def set_sixteen_vector(self, sixteen_vector: Tuple[float, ...]):
9898

9999
@staticmethod
100100
def compute_memory_depth(
101-
sixteen_vector: Dict[Tuple[Action, Action], float]
101+
sixteen_vector: Dict[Tuple[Action, Action], float],
102102
) -> int:
103103
values = set(list(sixteen_vector.values()))
104104

axelrod/strategies/momentum.py

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from axelrod.action import Action
2+
from axelrod.player import Player
3+
4+
C, D = Action.C, Action.D
5+
6+
7+
class Momentum(Player):
8+
"""
9+
This strategy is inspired by the concept of Gradual and the mathematical foundation of
10+
the Momentum optimizer used in deep learning.
11+
12+
The idea is that trust (or cooperation) evolves dynamically. A shift in trust can
13+
create significant and rapid changes in the player's behavior, much like how momentum
14+
responds to gradients in optimization.
15+
16+
Parameters:
17+
- alpha: Momentum decay factor that determines the rate of trust reduction. A higher value leads to slower decay, and the opponent's Defect acts as a trigger. (Optimized by Genetic Algorithm)
18+
- threshold: The minimum momentum required to continue cooperation. If momentum falls below this value, the strategy switches to Defect as punishment. (Optimized by Genetic Algorithm)
19+
- momentum: Represents the inertia of trust, dynamically changing based on past cooperation.
20+
21+
Names:
22+
- Momentum: Original name by Dong Won Moon
23+
24+
"""
25+
26+
name = "Momentum"
27+
classifier = {
28+
"memory_depth": float("inf"),
29+
"stochastic": False,
30+
"long_run_time": False,
31+
"inspects_source": False,
32+
"manipulates_source": False,
33+
"manipulates_state": False,
34+
}
35+
36+
def __init__(
37+
self,
38+
alpha=0.9914655399877477,
39+
threshold=0.9676595613724907,
40+
) -> None:
41+
super().__init__()
42+
self.alpha = alpha
43+
self.threshold = threshold
44+
self.momentum = 1.0
45+
46+
def __repr__(self):
47+
return f"Momentum: {self.momentum}, Alpha: {self.alpha}, Threshold: {self.threshold}"
48+
49+
def update_momentum(self, opponent_action):
50+
# If the opponent defects, the momentum decreases, reflecting a loss of trust.
51+
action_value = 1 if opponent_action == C else 0
52+
self.momentum = (
53+
self.alpha * self.momentum + (1 - self.alpha) * action_value
54+
)
55+
56+
def strategy(self, opponent: Player) -> Action:
57+
if len(self.history) == 0:
58+
self.momentum = 1.0
59+
return C
60+
61+
else:
62+
self.update_momentum(opponent.history[-1])
63+
return C if self.momentum >= self.threshold else D

axelrod/tests/strategies/test_gambler.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
"""Test for the Gambler strategy. Most tests come from the LookerUp test suite.
2-
"""
1+
"""Test for the Gambler strategy. Most tests come from the LookerUp test suite."""
32

43
import copy
54
import unittest
+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import axelrod as axl
2+
from axelrod import Action
3+
from axelrod.strategies.momentum import Momentum
4+
from axelrod.tests.strategies.test_player import TestPlayer
5+
6+
C, D = Action.C, Action.D
7+
8+
9+
class TestMomentum(TestPlayer):
10+
name = "Momentum"
11+
player = Momentum
12+
expected_classifier = {
13+
"memory_depth": float("inf"),
14+
"stochastic": False,
15+
"long_run_time": False,
16+
"inspects_source": False,
17+
"manipulates_source": False,
18+
"manipulates_state": False,
19+
}
20+
21+
def test_initialisation(self):
22+
player = self.player(alpha=0.9, threshold=0.8)
23+
self.assertEqual(player.alpha, 0.9)
24+
self.assertEqual(player.threshold, 0.8)
25+
self.assertEqual(player.momentum, 1.0)
26+
27+
def test_repr(self):
28+
player = self.player(alpha=0.9, threshold=0.8)
29+
self.assertEqual(
30+
repr(player), "Momentum: 1.0, Alpha: 0.9, Threshold: 0.8"
31+
)
32+
33+
def test_strategy(self):
34+
actions = [(C, C)]
35+
self.versus_test(
36+
axl.MockPlayer(actions=[C]),
37+
expected_actions=actions,
38+
init_kwargs={"alpha": 0.5, "threshold": 0.5},
39+
attrs={"momentum": 1.0},
40+
)
41+
42+
actions = [(C, D), (C, D), (D, D)]
43+
self.versus_test(
44+
axl.MockPlayer(actions=[D]),
45+
expected_actions=actions,
46+
init_kwargs={"alpha": 0.5, "threshold": 0.5},
47+
attrs={"momentum": 0.25},
48+
)
49+
50+
def test_vs_alternator(self):
51+
actions = [(C, C), (C, D), (C, C), (C, D), (D, C)]
52+
self.versus_test(
53+
axl.Alternator(),
54+
expected_actions=actions,
55+
init_kwargs={"alpha": 0.5, "threshold": 0.5},
56+
)
57+
58+
def test_vs_cooperator(self):
59+
actions = [(C, C), (C, C), (C, C), (C, C), (C, C)]
60+
self.versus_test(
61+
axl.Cooperator(),
62+
expected_actions=actions,
63+
init_kwargs={"alpha": 0.5, "threshold": 0.5},
64+
)
65+
66+
def test_vs_defector(self):
67+
actions = [(C, D), (C, D), (D, D), (D, D), (D, D)]
68+
self.versus_test(
69+
axl.Defector(),
70+
expected_actions=actions,
71+
init_kwargs={"alpha": 0.5, "threshold": 0.5},
72+
)
73+
74+
def test_vs_random(self):
75+
actions = [(C, D), (C, C), (C, C), (C, D), (D, D)]
76+
self.versus_test(
77+
axl.Random(),
78+
expected_actions=actions,
79+
seed=17,
80+
init_kwargs={"alpha": 0.5, "threshold": 0.5},
81+
)
82+
83+
def test_vs_random2(self):
84+
actions = [(C, C), (C, C), (C, C), (C, C)]
85+
self.versus_test(
86+
axl.Random(),
87+
expected_actions=actions,
88+
seed=3,
89+
init_kwargs={"alpha": 0.5, "threshold": 0.5},
90+
)

axelrod/tests/unit/test_resultset.py

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from collections import Counter
55

66
import pandas as pd
7-
from dask.dataframe.core import DataFrame
87
from hypothesis import given, settings
98
from numpy import mean, nanmedian, std
109

docs/index.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ Count the number of available players::
5353

5454
>>> import axelrod as axl
5555
>>> len(axl.strategies)
56-
241
56+
242
5757

5858
Create matches between two players::
5959

docs/reference/strategy_index.rst

+2
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ Here are the docstrings of all the strategies in the library.
7676
:members:
7777
.. automodule:: axelrod.strategies.memoryone
7878
:members:
79+
.. automodule:: axelrod.strategies.momentum
80+
:members:
7981
.. automodule:: axelrod.strategies.meta
8082
:members:
8183
.. automodule:: axelrod.strategies.mutual

run_mypy.py

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"axelrod/strategies/mathematicalconstants.py",
4242
"axelrod/strategies/memoryone.py",
4343
"axelrod/strategies/memorytwo.py",
44+
"axelrod/strategies/momentum.py",
4445
"axelrod/strategies/mutual.py",
4546
"axelrod/strategies/negation.py",
4647
"axelrod/strategies/oncebitten.py",

0 commit comments

Comments
 (0)