Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

This PR adds the Momentum strategy #1469

Merged
merged 12 commits into from
Feb 3, 2025
2 changes: 1 addition & 1 deletion axelrod/classifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ def __getitem__(
raise KeyError("Unknown classifier")

def classify_player_for_this_classifier(
player: Union[Player, Type[Player]]
player: Union[Player, Type[Player]],
) -> Any:
def try_lookup() -> Any:
try:
Expand Down
2 changes: 1 addition & 1 deletion axelrod/makes_use_of.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def makes_use_of(player: Type[Player]) -> Set[Text]:


def makes_use_of_variant(
player_or_method: Union[Callable, Type[Player]]
player_or_method: Union[Callable, Type[Player]],
) -> Set[Text]:
"""A version of makes_use_of that works on functions or player classes."""
try:
Expand Down
2 changes: 2 additions & 0 deletions axelrod/strategies/_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@
from .memorytwo import AON2, MEM2, DelayedAON1
from .memorytwo import MemoryTwoPlayer # pylint: disable=unused-import

from .momentum import Momentum
from .mutual import Desperate, Hopeless, Willing
from .negation import Negation
from .oncebitten import FoolMeOnce, ForgetfulFoolMeOnce, OnceBitten
Expand Down Expand Up @@ -402,6 +403,7 @@
MEM2,
MathConstantHunter,
Michaelos,
Momentum,
NTitsForMTats,
NaiveProber,
Negation,
Expand Down
2 changes: 1 addition & 1 deletion axelrod/strategies/frequency_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def __init__(self) -> None:
"""
super().__init__()
self.minimum_cooperation_ratio = 0.25
self.frequency_table = dict()
self.frequency_table: dict = dict()
self.last_sequence = ""
self.current_sequence = ""

Expand Down
2 changes: 1 addition & 1 deletion axelrod/strategies/memorytwo.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def set_sixteen_vector(self, sixteen_vector: Tuple[float, ...]):

@staticmethod
def compute_memory_depth(
sixteen_vector: Dict[Tuple[Action, Action], float]
sixteen_vector: Dict[Tuple[Action, Action], float],
) -> int:
values = set(list(sixteen_vector.values()))

Expand Down
63 changes: 63 additions & 0 deletions axelrod/strategies/momentum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from axelrod.action import Action
from axelrod.player import Player

C, D = Action.C, Action.D


class Momentum(Player):
"""
This strategy is inspired by the concept of Gradual and the mathematical foundation of
the Momentum optimizer used in deep learning.

The idea is that trust (or cooperation) evolves dynamically. A shift in trust can
create significant and rapid changes in the player's behavior, much like how momentum
responds to gradients in optimization.

Parameters:
- 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)
- 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)
- momentum: Represents the inertia of trust, dynamically changing based on past cooperation.

Names:
- Momentum: Original name by Dong Won Moon

"""

name = "Momentum"
classifier = {
"memory_depth": float("inf"),
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}

def __init__(
self,
alpha=0.9914655399877477,
threshold=0.9676595613724907,
) -> None:
super().__init__()
self.alpha = alpha
self.threshold = threshold
self.momentum = 1.0

def __repr__(self):
return f"Momentum: {self.momentum}, Alpha: {self.alpha}, Threshold: {self.threshold}"

def update_momentum(self, opponent_action):
# If the opponent defects, the momentum decreases, reflecting a loss of trust.
action_value = 1 if opponent_action == C else 0
self.momentum = (
self.alpha * self.momentum + (1 - self.alpha) * action_value
)

def strategy(self, opponent: Player) -> Action:
if len(self.history) == 0:
self.momentum = 1.0
return C

else:
self.update_momentum(opponent.history[-1])
return C if self.momentum >= self.threshold else D
3 changes: 1 addition & 2 deletions axelrod/tests/strategies/test_gambler.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""Test for the Gambler strategy. Most tests come from the LookerUp test suite.
"""
"""Test for the Gambler strategy. Most tests come from the LookerUp test suite."""

import copy
import unittest
Expand Down
90 changes: 90 additions & 0 deletions axelrod/tests/strategies/test_momentum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import axelrod as axl
from axelrod import Action
from axelrod.strategies.momentum import Momentum
from axelrod.tests.strategies.test_player import TestPlayer

C, D = Action.C, Action.D


class TestMomentum(TestPlayer):
name = "Momentum"
player = Momentum
expected_classifier = {
"memory_depth": float("inf"),
"stochastic": False,
"long_run_time": False,
"inspects_source": False,
"manipulates_source": False,
"manipulates_state": False,
}

def test_initialisation(self):
player = self.player(alpha=0.9, threshold=0.8)
self.assertEqual(player.alpha, 0.9)
self.assertEqual(player.threshold, 0.8)
self.assertEqual(player.momentum, 1.0)

def test_repr(self):
player = self.player(alpha=0.9, threshold=0.8)
self.assertEqual(
repr(player), "Momentum: 1.0, Alpha: 0.9, Threshold: 0.8"
)

def test_strategy(self):
actions = [(C, C)]
self.versus_test(
axl.MockPlayer(actions=[C]),
expected_actions=actions,
init_kwargs={"alpha": 0.5, "threshold": 0.5},
attrs={"momentum": 1.0},
)

actions = [(C, D), (C, D), (D, D)]
self.versus_test(
axl.MockPlayer(actions=[D]),
expected_actions=actions,
init_kwargs={"alpha": 0.5, "threshold": 0.5},
attrs={"momentum": 0.25},
)

def test_vs_alternator(self):
actions = [(C, C), (C, D), (C, C), (C, D), (D, C)]
self.versus_test(
axl.Alternator(),
expected_actions=actions,
init_kwargs={"alpha": 0.5, "threshold": 0.5},
)

def test_vs_cooperator(self):
actions = [(C, C), (C, C), (C, C), (C, C), (C, C)]
self.versus_test(
axl.Cooperator(),
expected_actions=actions,
init_kwargs={"alpha": 0.5, "threshold": 0.5},
)

def test_vs_defector(self):
actions = [(C, D), (C, D), (D, D), (D, D), (D, D)]
self.versus_test(
axl.Defector(),
expected_actions=actions,
init_kwargs={"alpha": 0.5, "threshold": 0.5},
)

def test_vs_random(self):
actions = [(C, D), (C, C), (C, C), (C, D), (D, D)]
self.versus_test(
axl.Random(),
expected_actions=actions,
seed=17,
init_kwargs={"alpha": 0.5, "threshold": 0.5},
)

def test_vs_random2(self):
actions = [(C, C), (C, C), (C, C), (C, C)]
self.versus_test(
axl.Random(),
expected_actions=actions,
seed=3,
init_kwargs={"alpha": 0.5, "threshold": 0.5},
)
1 change: 0 additions & 1 deletion axelrod/tests/unit/test_resultset.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from collections import Counter

import pandas as pd
from dask.dataframe.core import DataFrame
from hypothesis import given, settings
from numpy import mean, nanmedian, std

Expand Down
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Count the number of available players::

>>> import axelrod as axl
>>> len(axl.strategies)
241
242

Create matches between two players::

Expand Down
2 changes: 2 additions & 0 deletions docs/reference/strategy_index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ Here are the docstrings of all the strategies in the library.
:members:
.. automodule:: axelrod.strategies.memoryone
:members:
.. automodule:: axelrod.strategies.momentum
:members:
.. automodule:: axelrod.strategies.meta
:members:
.. automodule:: axelrod.strategies.mutual
Expand Down
1 change: 1 addition & 0 deletions run_mypy.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"axelrod/strategies/mathematicalconstants.py",
"axelrod/strategies/memoryone.py",
"axelrod/strategies/memorytwo.py",
"axelrod/strategies/momentum.py",
"axelrod/strategies/mutual.py",
"axelrod/strategies/negation.py",
"axelrod/strategies/oncebitten.py",
Expand Down