Skip to content

Commit ecc7104

Browse files
authored
Merge pull request #1397 from Axelrod-Python/add-custom-matches
Add custom matches
2 parents 4ab64ab + 9dc600f commit ecc7104

File tree

9 files changed

+116
-2
lines changed

9 files changed

+116
-2
lines changed

axelrod/moran.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def __init__(
2929
mutation_method="transition",
3030
stop_on_fixation=True,
3131
seed=None,
32+
match_class=Match,
3233
) -> None:
3334
"""
3435
An agent based Moran process class. In each round, each player plays a
@@ -106,6 +107,7 @@ def __init__(
106107
else:
107108
self.deterministic_cache = DeterministicCache()
108109
self.turns = turns
110+
self.match_class = match_class
109111
self.prob_end = prob_end
110112
self.game = game
111113
self.noise = noise
@@ -376,7 +378,7 @@ def score_all(self) -> List:
376378
for i, j in self._matchup_indices():
377379
player1 = self.players[i]
378380
player2 = self.players[j]
379-
match = Match(
381+
match = self.match_class(
380382
(player1, player2),
381383
turns=self.turns,
382384
prob_end=self.prob_end,

axelrod/tests/unit/test_moran.py

+19-1
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,25 @@ def test_different_game(self):
188188
# Possible for Cooperator to become fixed when using a different game
189189
p1, p2 = axl.Cooperator(), axl.Defector()
190190
game = axl.Game(r=4, p=2, s=1, t=6)
191-
mp = MoranProcess((p1, p2), turns=5, game=game, seed=88)
191+
mp = MoranProcess((p1, p2), turns=5, game=game, seed=3)
192+
populations = mp.play()
193+
self.assertEqual(mp.winning_strategy_name, str(p1))
194+
195+
def test_different_match(self):
196+
"""Test alternative Match class, mainly to show that the results are different
197+
than the results of `test_different_game` where the same seed is used."""
198+
# Using a different game where the scores are all constant
199+
class StandInMatch(axl.Match):
200+
"""A Match were all players get a score of 3"""
201+
202+
def final_score_per_turn(self):
203+
return 0, 3
204+
205+
p1, p2 = axl.Cooperator(), axl.Defector()
206+
game = axl.Game(r=4, p=2, s=1, t=6)
207+
mp = MoranProcess(
208+
(p1, p2), turns=5, game=game, match_class=StandInMatch, seed=3
209+
)
192210
populations = mp.play()
193211
self.assertEqual(mp.winning_strategy_name, str(p2))
194212

docs/how-to/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ with the Axelrod library.
2424
use_parallel_processing.rst
2525
use_a_cache.rst
2626
use_different_stage_games.rst
27+
use_custom_matches.rst
2728
set_a_seed.rst
2829
set_player_information.rst
2930
check_player_equality.rst

docs/how-to/use_custom_matches.rst

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
.. _use-custom-matches:
2+
3+
Use custom matches
4+
==================
5+
6+
The Moran process supports custom match classes. Below
7+
creates a new class of a match where both players end with a score of 2::
8+
9+
10+
>>> import axelrod as axl
11+
>>> class MassBaseMatch(axl.Match):
12+
... """Axelrod Match object with a modified final score function to enable mass to influence the final score as a multiplier"""
13+
... def final_score_per_turn(self):
14+
... return 2, 2
15+
16+
We then create a Moran process with the custom match class by passing our custom
17+
:code:`MassBaseMatch` to the Moran process with the :code:`match_class` keyword
18+
argument::
19+
20+
>>> players = [axl.Cooperator(), axl.Defector(), axl.TitForTat(), axl.Grudger()]
21+
>>> mp = axl.MoranProcess(players=players, match_class=MassBaseMatch, seed=0)
22+
>>> population = mp.play()
23+
>>> print(mp.winning_strategy_name)
24+
Defector

docs/reference/bibliography.rst

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ documentation.
3535
.. [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.
3636
.. [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/
3737
.. [Kraines1989] Kraines, David, and Vivian Kraines. "Pavlov and the prisoner's dilemma." Theory and decision 26.1 (1989): 47-79. doi:10.1007/BF00134056
38+
.. [Krapohl2020] Krapohl, S., Ocelík, V. & Walentek, D.M. The instability of globalization: applying evolutionary game theory to global trade cooperation. Public Choice 188, 31–51 (2021). https://doi.org/10.1007/s11127-020-00799-1
3839
.. [LessWrong2011] Zoo of Strategies (2011) LessWrong. Available at: http://lesswrong.com/lw/7f2/prisoners_dilemma_tournament_results/
3940
.. [Li2007] Li, J, How to Design a Strategy to Win an IPD Tournament, in Kendall G., Yao X. and Chong S. (eds.) The iterated prisoner’s dilemma: 20 years on. World Scientific, chapter 4, pp. 29-40, 2007.
4041
.. [Li2009] Li, J. & Kendall, G. (2009). A Strategy with Novel Evolutionary Features for the Iterated Prisoner’s Dilemma. Evolutionary Computation 17(2): 257–274.

docs/requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
docutils <= 0.17 # Added this for a problem with sphinx https://github.com/sphinx-doc/sphinx/commit/13803a79e7179f40a27f46d5a5a05f1eebbcbb63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
.. _create-heterogeneous-moran-processes:
2+
3+
Create Heterogeneous Moran Processes
4+
====================================
5+
6+
Axelrod Matches are homogeneous by nature but can be extended to utilize
7+
additional attributes of heterogeneous players. This tutorial indicates how the
8+
Axelrod :code:`Match` class can be manipulated in order to play heterogeneous
9+
tournaments and Moran processes using mass as a score modifier similarly to the
10+
work of [Krapohl2020]_.
11+
12+
The following lines of code creates a list of players from the available demo
13+
strategies along with an ascending list of masses we will use for the players.
14+
This is equivalent in principle to the country masses discussed in
15+
[Krapohl2020]_::
16+
17+
>>> import axelrod as axl
18+
>>> players = [player() for player in axl.demo_strategies]
19+
>>> masses = [i for i in range(len(players))]
20+
>>> players
21+
[Cooperator, Defector, Tit For Tat, Grudger, Random: 0.5]
22+
23+
Using the :code:`setattr()` function, additional attributes can be passed to
24+
players to enable access during matches and tournaments without manual
25+
modification of individual strategies::
26+
27+
>>> def set_player_mass(players, masses):
28+
... """Add mass attribute to player strategy classes to be accessable via self.mass"""
29+
... for player, mass in zip(players, masses):
30+
... setattr(player, "mass", mass)
31+
...
32+
>>> set_player_mass(players, masses)
33+
34+
The :code:`Match` class can be partially altered to enable different behaviour
35+
(see :ref:`use-custom-matches`).
36+
Here we extend :code:`axl.Match` and overwrite its
37+
:code:`final_score_per_turn()` function to utilize the player mass attribute as
38+
a multiplier for the final score::
39+
40+
>>> class MassBaseMatch(axl.Match):
41+
... """Axelrod Match object with a modified final score function to enable mass to influence the final score as a multiplier"""
42+
... def final_score_per_turn(self):
43+
... base_scores = axl.Match.final_score_per_turn(self)
44+
... return [player.mass * score for player, score in zip(self.players, base_scores)]
45+
46+
In [Krapohl2020]_ a non standard Moran process is used where the mass of
47+
individuals is not reproduced so we will use inheritance to create a new Moran
48+
process that keeps the mass of the individuals constant::
49+
50+
>>> class MassBasedMoranProcess(axl.MoranProcess):
51+
... """Axelrod MoranProcess class """
52+
... def __next__(self):
53+
... set_player_mass(self.players, masses)
54+
... super().__next__()
55+
... return self
56+
57+
>>> mp = MassBasedMoranProcess(players, match_class=MassBaseMatch, seed=0)
58+
>>> populations = mp.play()
59+
>>> print(mp.winning_strategy_name)
60+
Random: 0.5
61+
62+
Note that the snippets here only influence the final score of matches. The
63+
behavior of matches, and Moran processes can be more heavily influenced by
64+
partially overwriting other :code:`match` functions or :code:`birth` and
65+
:code:`death` functions within :code:`MoranProcess`.

docs/tutorials/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ Contents:
1212

1313
new_to_game_theory_and_or_python/index.rst
1414
running_axelrods_first_tournament/index.rst
15+
creating_heterogenous_player_moran_process/index.rst

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ cloudpickle>=0.2.2
33
fsspec>=0.6.0
44
toolz>=0.8.2
55

6+
docutils <= 0.17 # Added this for a problem with sphinx https://github.com/sphinx-doc/sphinx/commit/13803a79e7179f40a27f46d5a5a05f1eebbcbb63
67
dask>=2.9.2
78
hypothesis==5.19.3
89
matplotlib>=3.0.3

0 commit comments

Comments
 (0)