From 7d8083b28a4525d8000ed91a0b9589973b888fee Mon Sep 17 00:00:00 2001 From: Nikoleta Glynatsi Date: Mon, 20 Aug 2018 15:56:41 +0100 Subject: [PATCH 1/8] write test for fitness function moran process --- axelrod/tests/unit/test_moran.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/axelrod/tests/unit/test_moran.py b/axelrod/tests/unit/test_moran.py index c50571878..a2e35713f 100644 --- a/axelrod/tests/unit/test_moran.py +++ b/axelrod/tests/unit/test_moran.py @@ -37,6 +37,7 @@ def test_init(self): self.assertEqual(mp.interaction_graph._edges, [(0, 1), (1, 0)]) self.assertEqual(mp.reproduction_graph._edges, [(0, 1), (1, 0), (0, 0), (1, 1)]) + self.assertEqual(mp.fitness_function, None) self.assertEqual(mp.locations, [0, 1]) self.assertEqual(mp.index, {0: 0, 1: 1}) @@ -357,6 +358,16 @@ def test_population_plot(self): self.assertEqual(ax.get_xlim(), (-0.8, 16.8)) self.assertEqual(ax.get_ylim(), (0, 5.25)) + def test_fitness_to_moran(self): + axelrod.seed(0) + players = (axelrod.Cooperator(), axelrod.Defector(), + axelrod.Defector(), axelrod.Defector()) + w = 0.95 + fitness_function = lambda score: 1 - w + w * score + mp = MoranProcess(players, turns=10, fitness_function=fitness_function) + populations = mp.play() + self.assertEqual(mp.winning_strategy_name, 'Cooperator') + class GraphMoranProcess(unittest.TestCase): @@ -495,4 +506,4 @@ def test_getting_scores_from_cache(self): scores = self.amp._get_scores_from_cache(("Cooperator", "Defector")) self.assertEqual(scores, (0, 5)) scores = self.amp._get_scores_from_cache(("Defector", "Cooperator")) - self.assertEqual(scores, (5, 0)) + self.assertEqual(scores, (5, 0)) \ No newline at end of file From 421101be01912849c884460b15dfb872af2cb64e Mon Sep 17 00:00:00 2001 From: Nikoleta Glynatsi Date: Mon, 20 Aug 2018 16:08:33 +0100 Subject: [PATCH 2/8] implement fitness function for the moran process --- axelrod/moran.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/axelrod/moran.py b/axelrod/moran.py index ba7268180..dd592ac66 100644 --- a/axelrod/moran.py +++ b/axelrod/moran.py @@ -13,9 +13,10 @@ from .random_ import randrange from typing import List, Tuple, Set, Optional +from types import FunctionType -def fitness_proportionate_selection(scores: List) -> int: +def fitness_proportionate_selection(scores: List, fitness_function: FunctionType = None) -> int: """Randomly selects an individual proportionally to score. Parameters @@ -27,7 +28,10 @@ def fitness_proportionate_selection(scores: List) -> int: An index of the above list selected at random proportionally to the list element divided by the total. """ - csums = np.cumsum(scores) + if fitness_function is None: + csums = np.cumsum(scores) + else: + csums = np.cumsum([fitness_function(s) for s in scores]) total = csums[-1] r = random.random() * total @@ -44,7 +48,8 @@ def __init__(self, players: List[Player], turns: int = DEFAULT_TURNS, deterministic_cache: DeterministicCache = None, mutation_rate: float = 0., mode: str = 'bd', interaction_graph: Graph = None, - reproduction_graph: Graph = None) -> None: + reproduction_graph: Graph = None, + fitness_function: FunctionType = None) -> None: """ An agent based Moran process class. In each round, each player plays a Match with each other player. Players are assigned a fitness score by @@ -138,6 +143,7 @@ def __init__(self, players: List[Player], turns: int = DEFAULT_TURNS, assert list(v1) == list(v2) self.interaction_graph = interaction_graph self.reproduction_graph = reproduction_graph + self.fitness_function = fitness_function # Map players to graph vertices self.locations = sorted(interaction_graph.vertices()) self.index = dict(zip(sorted(interaction_graph.vertices()), @@ -212,11 +218,11 @@ def birth(self, index: int = None) -> int: # possible choices scores.pop(index) # Make sure to get the correct index post-pop - j = fitness_proportionate_selection(scores) + j = fitness_proportionate_selection(scores, fitness_function=self.fitness_function) if j >= index: j += 1 else: - j = fitness_proportionate_selection(scores) + j = fitness_proportionate_selection(scores, fitness_function=self.fitness_function) return j def fixation_check(self) -> bool: From 3a9b2abc748609a41c93b97c6c892e3fab7758df Mon Sep 17 00:00:00 2001 From: Nikoleta Glynatsi Date: Mon, 20 Aug 2018 16:08:59 +0100 Subject: [PATCH 3/8] rename test --- axelrod/tests/unit/test_moran.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/axelrod/tests/unit/test_moran.py b/axelrod/tests/unit/test_moran.py index a2e35713f..e69561677 100644 --- a/axelrod/tests/unit/test_moran.py +++ b/axelrod/tests/unit/test_moran.py @@ -358,8 +358,8 @@ def test_population_plot(self): self.assertEqual(ax.get_xlim(), (-0.8, 16.8)) self.assertEqual(ax.get_ylim(), (0, 5.25)) - def test_fitness_to_moran(self): - axelrod.seed(0) + def test_cooperator_can_win_with_fitness_function(self): + axelrod.seed(689) players = (axelrod.Cooperator(), axelrod.Defector(), axelrod.Defector(), axelrod.Defector()) w = 0.95 From 5eed9b4f337d4b4c5da788279030165a7cace1fe Mon Sep 17 00:00:00 2001 From: Nikoleta Glynatsi Date: Mon, 20 Aug 2018 16:22:46 +0100 Subject: [PATCH 4/8] add documentation --- docs/reference/bibliography.rst | 1 + docs/tutorials/getting_started/moran.rst | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/docs/reference/bibliography.rst b/docs/reference/bibliography.rst index bb97f4930..2dd38cce1 100644 --- a/docs/reference/bibliography.rst +++ b/docs/reference/bibliography.rst @@ -47,6 +47,7 @@ documentation. .. [Nowak1990] Nowak, M., & Sigmund, K. (1990). The evolution of stochastic strategies in the Prisoner's Dilemma. Acta Applicandae Mathematica. https://link.springer.com/article/10.1007/BF00049570 .. [Nowak1992] Nowak, M.., & May, R. M. (1992). Evolutionary games and spatial chaos. Nature. http://doi.org/10.1038/359826a0 .. [Nowak1993] Nowak, M., & Sigmund, K. (1993). A strategy of win-stay, lose-shift that outperforms tit-for-tat in the Prisoner’s Dilemma game. Nature, 364(6432), 56–58. http://doi.org/10.1038/364056a0 +.. [Ohtsuki2006] Ohtsuki, Hisashi, et al. "A simple rule for the evolution of cooperation on graphs and social networks." Nature 441.7092 (2006): 502. .. [PD2017] http://www.prisoners-dilemma.com/competition.html (Accessed: 6 June 2017) .. [Press2012] Press, W. H., & Dyson, F. J. (2012). Iterated Prisoner’s Dilemma contains strategies that dominate any evolutionary opponent. Proceedings of the National Academy of Sciences, 109(26), 10409–10413. http://doi.org/10.1073/pnas.1206569109 .. [Prison1998] LIFL (1998) PRISON. Available at: http://www.lifl.fr/IPD/ipd.frame.html (Accessed: 19 September 2016). diff --git a/docs/tutorials/getting_started/moran.rst b/docs/tutorials/getting_started/moran.rst index 13d3e05ab..67f3185e4 100644 --- a/docs/tutorials/getting_started/moran.rst +++ b/docs/tutorials/getting_started/moran.rst @@ -115,6 +115,19 @@ function like :code:`takewhile` from :code:`itertools`):: >>> mp.population_distribution() Counter({'Grudger': 4}) +It is possible to pass a fitness function that scales the utility values. A common one +used in the literature, [Ohtsuki2006]_, is :math:`f(s) = 1 - w + ws` where :math:`w` +denotes the intensity of selection:: + + >>> axl.seed(689) + >>> players = (axl.Cooperator(), axl.Defector(), axl.Defector(), axl.Defector()) + >>> w = 0.95 + >>> fitness_function = lambda score: 1 - w + w * score + >>> mp = axl.MoranProcess(players, turns=10, fitness_function=fitness_function) + >>> populations = mp.play() + >>> mp.winning_strategy_name + 'Cooperator' + Other types of implemented Moran processes: - :ref:`moran-process-on-graphs` From 7a1a17aa0a533d21163fb4982f009b82873548e7 Mon Sep 17 00:00:00 2001 From: Nikoleta Glynatsi Date: Mon, 20 Aug 2018 16:27:33 +0100 Subject: [PATCH 5/8] run black and isort --- axelrod/moran.py | 107 +++++++++++++++-------- axelrod/tests/unit/test_moran.py | 140 ++++++++++++++++++------------- 2 files changed, 154 insertions(+), 93 deletions(-) diff --git a/axelrod/moran.py b/axelrod/moran.py index dd592ac66..336157636 100644 --- a/axelrod/moran.py +++ b/axelrod/moran.py @@ -1,22 +1,24 @@ """Implementation of the Moran process on Graphs.""" -from collections import Counter import random +from collections import Counter +from types import FunctionType +from typing import List, Optional, Set, Tuple import matplotlib.pyplot as plt import numpy as np -from axelrod import DEFAULT_TURNS, Player, Game +from axelrod import DEFAULT_TURNS, Game, Player + from .deterministic_cache import DeterministicCache -from .graph import complete_graph, Graph +from .graph import Graph, complete_graph from .match import Match from .random_ import randrange -from typing import List, Tuple, Set, Optional -from types import FunctionType - -def fitness_proportionate_selection(scores: List, fitness_function: FunctionType = None) -> int: +def fitness_proportionate_selection( + scores: List, fitness_function: FunctionType = None +) -> int: """Randomly selects an individual proportionally to score. Parameters @@ -42,14 +44,20 @@ def fitness_proportionate_selection(scores: List, fitness_function: FunctionType class MoranProcess(object): - def __init__(self, players: List[Player], turns: int = DEFAULT_TURNS, - prob_end: float = None, noise: float = 0, - game: Game = None, - deterministic_cache: DeterministicCache = None, - mutation_rate: float = 0., mode: str = 'bd', - interaction_graph: Graph = None, - reproduction_graph: Graph = None, - fitness_function: FunctionType = None) -> None: + def __init__( + self, + players: List[Player], + turns: int = DEFAULT_TURNS, + prob_end: float = None, + noise: float = 0, + game: Game = None, + deterministic_cache: DeterministicCache = None, + mutation_rate: float = 0., + mode: str = "bd", + interaction_graph: Graph = None, + reproduction_graph: Graph = None, + fitness_function: FunctionType = None, + ) -> None: """ An agent based Moran process class. In each round, each player plays a Match with each other player. Players are assigned a fitness score by @@ -112,7 +120,7 @@ def __init__(self, players: List[Player], turns: int = DEFAULT_TURNS, assert (mutation_rate >= 0) and (mutation_rate <= 1) assert (noise >= 0) and (noise <= 1) mode = mode.lower() - assert mode in ['bd', 'db'] + assert mode in ["bd", "db"] self.mode = mode if deterministic_cache is not None: self.deterministic_cache = deterministic_cache @@ -128,14 +136,17 @@ def __init__(self, players: List[Player], turns: int = DEFAULT_TURNS, d[str(p)] = p mutation_targets = dict() for key in sorted(keys): - mutation_targets[key] = [v for (k, v) in sorted(d.items()) if k != key] + mutation_targets[key] = [ + v for (k, v) in sorted(d.items()) if k != key + ] self.mutation_targets = mutation_targets if interaction_graph is None: interaction_graph = complete_graph(len(players), loops=False) if reproduction_graph is None: - reproduction_graph = Graph(interaction_graph.edges(), - directed=interaction_graph.directed) + reproduction_graph = Graph( + interaction_graph.edges(), directed=interaction_graph.directed + ) reproduction_graph.add_loops() # Check equal vertices v1 = interaction_graph.vertices() @@ -146,8 +157,9 @@ def __init__(self, players: List[Player], turns: int = DEFAULT_TURNS, self.fitness_function = fitness_function # Map players to graph vertices self.locations = sorted(interaction_graph.vertices()) - self.index = dict(zip(sorted(interaction_graph.vertices()), - range(len(players)))) + self.index = dict( + zip(sorted(interaction_graph.vertices()), range(len(players))) + ) def set_players(self) -> None: """Copy the initial players into the first population.""" @@ -198,8 +210,11 @@ def death(self, index: int = None) -> int: else: # Select locally # index is not None in this case - vertex = random.choice(sorted( - self.reproduction_graph.out_vertices(self.locations[index]))) + vertex = random.choice( + sorted( + self.reproduction_graph.out_vertices(self.locations[index]) + ) + ) i = self.index[vertex] return i @@ -218,11 +233,15 @@ def birth(self, index: int = None) -> int: # possible choices scores.pop(index) # Make sure to get the correct index post-pop - j = fitness_proportionate_selection(scores, fitness_function=self.fitness_function) + j = fitness_proportionate_selection( + scores, fitness_function=self.fitness_function + ) if j >= index: j += 1 else: - j = fitness_proportionate_selection(scores, fitness_function=self.fitness_function) + j = fitness_proportionate_selection( + scores, fitness_function=self.fitness_function + ) return j def fixation_check(self) -> bool: @@ -327,11 +346,14 @@ def score_all(self) -> List: for i, j in self._matchup_indices(): player1 = self.players[i] player2 = self.players[j] - match = Match((player1, player2), - turns=self.turns, prob_end=self.prob_end, - noise=self.noise, - game=self.game, - deterministic_cache=self.deterministic_cache) + match = Match( + (player1, player2), + turns=self.turns, + prob_end=self.prob_end, + noise=self.noise, + game=self.game, + deterministic_cache=self.deterministic_cache, + ) match.play() match_scores = match.final_score_per_turn() scores[i] += match_scores[0] @@ -379,7 +401,8 @@ def play(self) -> List[Counter]: if self.mutation_rate != 0: raise ValueError( "MoranProcess.play() will never exit if mutation_rate is" - "nonzero. Use iteration instead.") + "nonzero. Use iteration instead." + ) while True: try: self.__next__() @@ -441,8 +464,13 @@ class ApproximateMoranProcess(MoranProcess): Instead of playing the matches, the result is sampled from a dictionary of player tuples to distribution of match outcomes """ - def __init__(self, players: List[Player], cached_outcomes: dict, - mutation_rate: float = 0) -> None: + + def __init__( + self, + players: List[Player], + cached_outcomes: dict, + mutation_rate: float = 0, + ) -> None: """ Parameters ---------- @@ -454,8 +482,12 @@ def __init__(self, players: List[Player], cached_outcomes: dict, probability `mutation_rate` """ super(ApproximateMoranProcess, self).__init__( - players, turns=0, noise=0, deterministic_cache=None, - mutation_rate=mutation_rate) + players, + turns=0, + noise=0, + deterministic_cache=None, + mutation_rate=mutation_rate, + ) self.cached_outcomes = cached_outcomes def score_all(self) -> List: @@ -472,8 +504,9 @@ def score_all(self) -> List: scores = [0] * N for i in range(N): for j in range(i + 1, N): - player_names = tuple([str(self.players[i]), - str(self.players[j])]) + player_names = tuple( + [str(self.players[i]), str(self.players[j])] + ) cached_score = self._get_scores_from_cache(player_names) scores[i] += cached_score[0] diff --git a/axelrod/tests/unit/test_moran.py b/axelrod/tests/unit/test_moran.py index e69561677..5e9b92e0b 100644 --- a/axelrod/tests/unit/test_moran.py +++ b/axelrod/tests/unit/test_moran.py @@ -1,22 +1,20 @@ -from collections import Counter import itertools import random import unittest - -from hypothesis import given, example, settings +from collections import Counter import matplotlib.pyplot as plt import axelrod -from axelrod import MoranProcess, ApproximateMoranProcess, Pdf +from axelrod import ApproximateMoranProcess, MoranProcess, Pdf from axelrod.moran import fitness_proportionate_selection from axelrod.tests.property import strategy_lists +from hypothesis import example, given, settings C, D = axelrod.Action.C, axelrod.Action.D class TestMoranProcess(unittest.TestCase): - def test_init(self): players = axelrod.Cooperator(), axelrod.Defector() mp = MoranProcess(players) @@ -26,17 +24,21 @@ def test_init(self): self.assertEqual(mp.noise, 0) self.assertEqual(mp.initial_players, players) self.assertEqual(mp.players, list(players)) - self.assertEqual(mp.populations, - [Counter({'Cooperator': 1, 'Defector': 1})]) + self.assertEqual( + mp.populations, [Counter({"Cooperator": 1, "Defector": 1})] + ) self.assertIsNone(mp.winning_strategy_name) self.assertEqual(mp.mutation_rate, 0) - self.assertEqual(mp.mode, 'bd') + self.assertEqual(mp.mode, "bd") self.assertEqual(mp.deterministic_cache, axelrod.DeterministicCache()) - self.assertEqual(mp.mutation_targets, - {'Cooperator': [players[1]], 'Defector': [players[0]]}) + self.assertEqual( + mp.mutation_targets, + {"Cooperator": [players[1]], "Defector": [players[0]]}, + ) self.assertEqual(mp.interaction_graph._edges, [(0, 1), (1, 0)]) - self.assertEqual(mp.reproduction_graph._edges, - [(0, 1), (1, 0), (0, 0), (1, 1)]) + self.assertEqual( + mp.reproduction_graph._edges, [(0, 1), (1, 0), (0, 0), (1, 1)] + ) self.assertEqual(mp.fitness_function, None) self.assertEqual(mp.locations, [0, 1]) self.assertEqual(mp.index, {0: 0, 1: 1}) @@ -47,11 +49,14 @@ def test_init(self): graph = axelrod.graph.Graph(edges, directed=True) mp = MoranProcess(players, interaction_graph=graph) self.assertEqual(mp.interaction_graph._edges, [(0, 1), (2, 0), (1, 2)]) - self.assertEqual(sorted(mp.reproduction_graph._edges), - sorted([(0, 1), (2, 0), (1, 2), (0, 0), (1, 1), (2, 2)])) - - mp = MoranProcess(players, interaction_graph=graph, - reproduction_graph=graph) + self.assertEqual( + sorted(mp.reproduction_graph._edges), + sorted([(0, 1), (2, 0), (1, 2), (0, 0), (1, 1), (2, 2)]), + ) + + mp = MoranProcess( + players, interaction_graph=graph, reproduction_graph=graph + ) self.assertEqual(mp.interaction_graph._edges, [(0, 1), (2, 0), (1, 2)]) self.assertEqual(mp.reproduction_graph._edges, [(0, 1), (2, 0), (1, 2)]) @@ -183,7 +188,7 @@ def test_death_birth(self): seeds = range(0, 20) for seed in seeds: axelrod.seed(seed) - mp = MoranProcess((p1, p2), mode='db') + mp = MoranProcess((p1, p2), mode="db") next(mp) self.assertIsNotNone(mp.winning_strategy_name) @@ -198,11 +203,11 @@ def test_death_birth_outcomes(self): players.append(axelrod.Defector()) for seed, outcome in seeds: axelrod.seed(seed) - mp = MoranProcess(players, mode='bd') + mp = MoranProcess(players, mode="bd") mp.play() winner = mp.winning_strategy_name axelrod.seed(seed) - mp = MoranProcess(players, mode='db') + mp = MoranProcess(players, mode="db") mp.play() winner2 = mp.winning_strategy_name self.assertEqual((winner == winner2), outcome) @@ -221,18 +226,21 @@ def test_two_players_with_mutation(self): p1, p2 = axelrod.Cooperator(), axelrod.Defector() axelrod.seed(5) mp = MoranProcess((p1, p2), mutation_rate=0.2) - self.assertDictEqual(mp.mutation_targets, - {str(p1): [p2], str(p2): [p1]}) + self.assertDictEqual( + mp.mutation_targets, {str(p1): [p2], str(p2): [p1]} + ) # Test that mutation causes the population to alternate between # fixations counters = [ - Counter({'Cooperator': 2}), - Counter({'Defector': 2}), - Counter({'Cooperator': 2}), - Counter({'Defector': 2}) + Counter({"Cooperator": 2}), + Counter({"Defector": 2}), + Counter({"Cooperator": 2}), + Counter({"Defector": 2}), ] for counter in counters: - for _ in itertools.takewhile(lambda x: x.population_distribution() != counter, mp): + for _ in itertools.takewhile( + lambda x: x.population_distribution() != counter, mp + ): pass self.assertEqual(mp.population_distribution(), counter) @@ -243,8 +251,11 @@ def test_play_exception(self): mp.play() def test_three_players(self): - players = [axelrod.Cooperator(), axelrod.Cooperator(), - axelrod.Defector()] + players = [ + axelrod.Cooperator(), + axelrod.Cooperator(), + axelrod.Defector(), + ] axelrod.seed(11) mp = MoranProcess(players) populations = mp.play() @@ -259,16 +270,17 @@ def test_three_players_with_mutation(self): p3 = axelrod.Defector() players = [p1, p2, p3] mp = MoranProcess(players, mutation_rate=0.2) - self.assertDictEqual(mp.mutation_targets, { - str(p1): [p3, p2], str(p2): [p1, p3], str(p3): [p1, p2]}) + self.assertDictEqual( + mp.mutation_targets, + {str(p1): [p3, p2], str(p2): [p1, p3], str(p3): [p1, p2]}, + ) # Test that mutation causes the population to alternate between # fixations - counters = [ - Counter({'Cooperator': 3}), - Counter({'Defector': 3}), - ] + counters = [Counter({"Cooperator": 3}), Counter({"Defector": 3})] for counter in counters: - for _ in itertools.takewhile(lambda x: x.population_distribution() != counter, mp): + for _ in itertools.takewhile( + lambda x: x.population_distribution() != counter, mp + ): pass self.assertEqual(mp.population_distribution(), counter) @@ -315,8 +327,12 @@ def test_reset(self): def test_constant_fitness_case(self): # Scores between an Alternator and Defector will be: (1, 6) axelrod.seed(0) - players = (axelrod.Alternator(), axelrod.Alternator(), - axelrod.Defector(), axelrod.Defector()) + players = ( + axelrod.Alternator(), + axelrod.Alternator(), + axelrod.Defector(), + axelrod.Defector(), + ) mp = MoranProcess(players, turns=2) winners = [] for _ in range(100): @@ -360,17 +376,20 @@ def test_population_plot(self): def test_cooperator_can_win_with_fitness_function(self): axelrod.seed(689) - players = (axelrod.Cooperator(), axelrod.Defector(), - axelrod.Defector(), axelrod.Defector()) + players = ( + axelrod.Cooperator(), + axelrod.Defector(), + axelrod.Defector(), + axelrod.Defector(), + ) w = 0.95 fitness_function = lambda score: 1 - w + w * score mp = MoranProcess(players, turns=10, fitness_function=fitness_function) populations = mp.play() - self.assertEqual(mp.winning_strategy_name, 'Cooperator') + self.assertEqual(mp.winning_strategy_name, "Cooperator") class GraphMoranProcess(unittest.TestCase): - def test_complete(self): """A complete graph should produce the same results as the default case.""" @@ -428,13 +447,15 @@ def test_asymmetry(self): players.append(axelrod.Defector()) for seed, outcome in seeds: axelrod.seed(seed) - mp = MoranProcess(players, interaction_graph=graph1, - reproduction_graph=graph2) + mp = MoranProcess( + players, interaction_graph=graph1, reproduction_graph=graph2 + ) mp.play() winner = mp.winning_strategy_name axelrod.seed(seed) - mp = MoranProcess(players, interaction_graph=graph2, - reproduction_graph=graph1) + mp = MoranProcess( + players, interaction_graph=graph2, reproduction_graph=graph1 + ) mp.play() winner2 = mp.winning_strategy_name self.assertEqual((winner == winner2), outcome) @@ -452,11 +473,11 @@ def test_cycle_death_birth(self): players.append(axelrod.Defector()) for seed, outcome in seeds: axelrod.seed(seed) - mp = MoranProcess(players, interaction_graph=graph, mode='bd') + mp = MoranProcess(players, interaction_graph=graph, mode="bd") mp.play() winner = mp.winning_strategy_name axelrod.seed(seed) - mp = MoranProcess(players, interaction_graph=graph, mode='db') + mp = MoranProcess(players, interaction_graph=graph, mode="db") mp.play() winner2 = mp.winning_strategy_name self.assertEqual((winner == winner2), outcome) @@ -464,29 +485,36 @@ def test_cycle_death_birth(self): class TestApproximateMoranProcess(unittest.TestCase): """A suite of tests for the ApproximateMoranProcess""" + players = [axelrod.Cooperator(), axelrod.Defector()] cached_outcomes = {} counter = Counter([(0, 5)]) pdf = Pdf(counter) - cached_outcomes[('Cooperator', 'Defector')] = pdf + cached_outcomes[("Cooperator", "Defector")] = pdf counter = Counter([(3, 3)]) pdf = Pdf(counter) - cached_outcomes[('Cooperator', 'Cooperator')] = pdf + cached_outcomes[("Cooperator", "Cooperator")] = pdf counter = Counter([(1, 1)]) pdf = Pdf(counter) - cached_outcomes[('Defector', 'Defector')] = pdf + cached_outcomes[("Defector", "Defector")] = pdf amp = ApproximateMoranProcess(players, cached_outcomes) def test_init(self): """Test the initialisation process""" - self.assertEqual(set(self.amp.cached_outcomes.keys()), - set([('Cooperator', 'Defector'), - ('Cooperator', 'Cooperator'), - ('Defector', 'Defector')])) + self.assertEqual( + set(self.amp.cached_outcomes.keys()), + set( + [ + ("Cooperator", "Defector"), + ("Cooperator", "Cooperator"), + ("Defector", "Defector"), + ] + ), + ) self.assertEqual(self.amp.players, self.players) self.assertEqual(self.amp.turns, 0) self.assertEqual(self.amp.noise, 0) @@ -506,4 +534,4 @@ def test_getting_scores_from_cache(self): scores = self.amp._get_scores_from_cache(("Cooperator", "Defector")) self.assertEqual(scores, (0, 5)) scores = self.amp._get_scores_from_cache(("Defector", "Cooperator")) - self.assertEqual(scores, (5, 0)) \ No newline at end of file + self.assertEqual(scores, (5, 0)) From d54c0c39eb6ac958c23eb7c4f406a596b1ac6cbe Mon Sep 17 00:00:00 2001 From: Nikoleta Glynatsi Date: Mon, 20 Aug 2018 22:01:54 +0100 Subject: [PATCH 6/8] add fitness_function to the docstrings --- axelrod/moran.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/axelrod/moran.py b/axelrod/moran.py index 336157636..e15e91942 100644 --- a/axelrod/moran.py +++ b/axelrod/moran.py @@ -24,6 +24,7 @@ def fitness_proportionate_selection( Parameters ---------- scores: Any sequence of real numbers + fitness_function: A function mapping a score to a (non-negative) float Returns ------- @@ -105,6 +106,8 @@ def __init__( reproduction_graph: Axelrod.graph.Graph The reproduction graph, set equal to the interaction graph if not given + fitness_function: + A function mapping a score to a (non-negative) float """ self.turns = turns self.prob_end = prob_end From 8824c9b8e1409f7fb414b0bafd9499bf1b6cba7f Mon Sep 17 00:00:00 2001 From: Nikoleta Glynatsi Date: Thu, 23 Aug 2018 10:12:54 -0400 Subject: [PATCH 7/8] fix type hint error --- axelrod/moran.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/axelrod/moran.py b/axelrod/moran.py index e15e91942..c2e8e3ce3 100644 --- a/axelrod/moran.py +++ b/axelrod/moran.py @@ -2,8 +2,7 @@ import random from collections import Counter -from types import FunctionType -from typing import List, Optional, Set, Tuple +from typing import Callable, List, Optional, Set, Tuple import matplotlib.pyplot as plt import numpy as np @@ -17,7 +16,7 @@ def fitness_proportionate_selection( - scores: List, fitness_function: FunctionType = None + scores: List, fitness_function: Callable = None ) -> int: """Randomly selects an individual proportionally to score. @@ -57,7 +56,7 @@ def __init__( mode: str = "bd", interaction_graph: Graph = None, reproduction_graph: Graph = None, - fitness_function: FunctionType = None, + fitness_function: Callable = None, ) -> None: """ An agent based Moran process class. In each round, each player plays a From c3724f2c7999bf9b6fe6f95e272d413fdc89a786 Mon Sep 17 00:00:00 2001 From: Nikoleta Glynatsi Date: Fri, 24 Aug 2018 09:34:51 -0400 Subject: [PATCH 8/8] rename fitness_function to fitness_transformation --- axelrod/moran.py | 35 +++++++++--------------- axelrod/tests/unit/test_moran.py | 8 +++--- docs/tutorials/getting_started/moran.rst | 4 +-- 3 files changed, 19 insertions(+), 28 deletions(-) diff --git a/axelrod/moran.py b/axelrod/moran.py index c2e8e3ce3..24693007a 100644 --- a/axelrod/moran.py +++ b/axelrod/moran.py @@ -16,24 +16,24 @@ def fitness_proportionate_selection( - scores: List, fitness_function: Callable = None + scores: List, fitness_transformation: Callable = None ) -> int: """Randomly selects an individual proportionally to score. Parameters ---------- scores: Any sequence of real numbers - fitness_function: A function mapping a score to a (non-negative) float + fitness_transformation: A function mapping a score to a (non-negative) float Returns ------- An index of the above list selected at random proportionally to the list element divided by the total. """ - if fitness_function is None: + if fitness_transformation is None: csums = np.cumsum(scores) else: - csums = np.cumsum([fitness_function(s) for s in scores]) + csums = np.cumsum([fitness_transformation(s) for s in scores]) total = csums[-1] r = random.random() * total @@ -56,7 +56,7 @@ def __init__( mode: str = "bd", interaction_graph: Graph = None, reproduction_graph: Graph = None, - fitness_function: Callable = None, + fitness_transformation: Callable = None, ) -> None: """ An agent based Moran process class. In each round, each player plays a @@ -105,7 +105,7 @@ def __init__( reproduction_graph: Axelrod.graph.Graph The reproduction graph, set equal to the interaction graph if not given - fitness_function: + fitness_transformation: A function mapping a score to a (non-negative) float """ self.turns = turns @@ -138,9 +138,7 @@ def __init__( d[str(p)] = p mutation_targets = dict() for key in sorted(keys): - mutation_targets[key] = [ - v for (k, v) in sorted(d.items()) if k != key - ] + mutation_targets[key] = [v for (k, v) in sorted(d.items()) if k != key] self.mutation_targets = mutation_targets if interaction_graph is None: @@ -156,7 +154,7 @@ def __init__( assert list(v1) == list(v2) self.interaction_graph = interaction_graph self.reproduction_graph = reproduction_graph - self.fitness_function = fitness_function + self.fitness_transformation = fitness_transformation # Map players to graph vertices self.locations = sorted(interaction_graph.vertices()) self.index = dict( @@ -213,9 +211,7 @@ def death(self, index: int = None) -> int: # Select locally # index is not None in this case vertex = random.choice( - sorted( - self.reproduction_graph.out_vertices(self.locations[index]) - ) + sorted(self.reproduction_graph.out_vertices(self.locations[index])) ) i = self.index[vertex] return i @@ -236,13 +232,13 @@ def birth(self, index: int = None) -> int: scores.pop(index) # Make sure to get the correct index post-pop j = fitness_proportionate_selection( - scores, fitness_function=self.fitness_function + scores, fitness_transformation=self.fitness_transformation ) if j >= index: j += 1 else: j = fitness_proportionate_selection( - scores, fitness_function=self.fitness_function + scores, fitness_transformation=self.fitness_transformation ) return j @@ -468,10 +464,7 @@ class ApproximateMoranProcess(MoranProcess): """ def __init__( - self, - players: List[Player], - cached_outcomes: dict, - mutation_rate: float = 0, + self, players: List[Player], cached_outcomes: dict, mutation_rate: float = 0 ) -> None: """ Parameters @@ -506,9 +499,7 @@ def score_all(self) -> List: scores = [0] * N for i in range(N): for j in range(i + 1, N): - player_names = tuple( - [str(self.players[i]), str(self.players[j])] - ) + player_names = tuple([str(self.players[i]), str(self.players[j])]) cached_score = self._get_scores_from_cache(player_names) scores[i] += cached_score[0] diff --git a/axelrod/tests/unit/test_moran.py b/axelrod/tests/unit/test_moran.py index 5e9b92e0b..120d4517a 100644 --- a/axelrod/tests/unit/test_moran.py +++ b/axelrod/tests/unit/test_moran.py @@ -39,7 +39,7 @@ def test_init(self): self.assertEqual( mp.reproduction_graph._edges, [(0, 1), (1, 0), (0, 0), (1, 1)] ) - self.assertEqual(mp.fitness_function, None) + self.assertEqual(mp.fitness_transformation, None) self.assertEqual(mp.locations, [0, 1]) self.assertEqual(mp.index, {0: 0, 1: 1}) @@ -374,7 +374,7 @@ def test_population_plot(self): self.assertEqual(ax.get_xlim(), (-0.8, 16.8)) self.assertEqual(ax.get_ylim(), (0, 5.25)) - def test_cooperator_can_win_with_fitness_function(self): + def test_cooperator_can_win_with_fitness_transformation(self): axelrod.seed(689) players = ( axelrod.Cooperator(), @@ -383,8 +383,8 @@ def test_cooperator_can_win_with_fitness_function(self): axelrod.Defector(), ) w = 0.95 - fitness_function = lambda score: 1 - w + w * score - mp = MoranProcess(players, turns=10, fitness_function=fitness_function) + fitness_transformation = lambda score: 1 - w + w * score + mp = MoranProcess(players, turns=10, fitness_transformation=fitness_transformation) populations = mp.play() self.assertEqual(mp.winning_strategy_name, "Cooperator") diff --git a/docs/tutorials/getting_started/moran.rst b/docs/tutorials/getting_started/moran.rst index 67f3185e4..d57e779c8 100644 --- a/docs/tutorials/getting_started/moran.rst +++ b/docs/tutorials/getting_started/moran.rst @@ -122,8 +122,8 @@ denotes the intensity of selection:: >>> axl.seed(689) >>> players = (axl.Cooperator(), axl.Defector(), axl.Defector(), axl.Defector()) >>> w = 0.95 - >>> fitness_function = lambda score: 1 - w + w * score - >>> mp = axl.MoranProcess(players, turns=10, fitness_function=fitness_function) + >>> fitness_transformation = lambda score: 1 - w + w * score + >>> mp = axl.MoranProcess(players, turns=10, fitness_transformation=fitness_transformation) >>> populations = mp.play() >>> mp.winning_strategy_name 'Cooperator'