Create Heterogeneous Moran Processes

Axelrod Matches are homogeneous by nature but can be extended to utilize additional attributes of heterogeneous players. This tutorial indicates how the Axelrod Match class can be manipulated in order to play heterogeneous tournaments and Moran processes using mass as a score modifier similarly to the work of [Krapohl2020].

The following lines of code creates a list of players from the available demo strategies along with an ascending list of masses we will use for the players. This is equivalent in principle to the country masses discussed in [Krapohl2020]:

>>> import axelrod as axl
>>> players = [player() for player in axl.demo_strategies]
>>> masses = [i for i in range(len(players))]
>>> players
[Cooperator, Defector, Tit For Tat, Grudger, Random: 0.5]

Using the setattr() function, additional attributes can be passed to players to enable access during matches and tournaments without manual modification of individual strategies:

>>> def set_player_mass(players, masses):
...     """Add mass attribute to player strategy classes to be accessable via self.mass"""
...     for player, mass in zip(players, masses):
...         setattr(player, "mass", mass)
...
>>> set_player_mass(players, masses)

The Match class can be partially altered to enable different behaviour (see Use custom matches). Here we extend axl.Match and overwrite its final_score_per_turn() function to utilize the player mass attribute as a multiplier for the final score:

>>> class MassBaseMatch(axl.Match):
...     """Axelrod Match object with a modified final score function to enable mass to influence the final score as a multiplier"""
...     def final_score_per_turn(self):
...         base_scores = axl.Match.final_score_per_turn(self)
...         return [player.mass * score for player, score in zip(self.players, base_scores)]

In [Krapohl2020] a non standard Moran process is used where the mass of individuals is not reproduced so we will use inheritance to create a new Moran process that keeps the mass of the individuals constant:

>>> class MassBasedMoranProcess(axl.MoranProcess):
...     """Axelrod MoranProcess class """
...     def __next__(self):
...         set_player_mass(self.players, masses)
...         super().__next__()
...         return self

>>> mp = MassBasedMoranProcess(players, match_class=MassBaseMatch, seed=0)
>>> populations = mp.play()
>>> print(mp.winning_strategy_name)
Random: 0.5

Note that the snippets here only influence the final score of matches. The behavior of matches, and Moran processes can be more heavily influenced by partially overwriting other match functions or birth and death functions within MoranProcess.