Skip to content

Commit

Permalink
Merge pull request #757 from Axelrod-Python/mwe
Browse files Browse the repository at this point in the history
Meta Winner Ensemble
  • Loading branch information
drvinceknight authored Nov 5, 2016
2 parents da920ec + 2dd113b commit 2ab2a9f
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 19 deletions.
5 changes: 3 additions & 2 deletions axelrod/strategies/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@
MetaPlayer, MetaMajority, MetaMinority, MetaWinner, MetaHunter,
MetaMajorityMemoryOne, MetaWinnerMemoryOne, MetaMajorityFiniteMemory,
MetaWinnerFiniteMemory, MetaMajorityLongMemory, MetaWinnerLongMemory,
MetaMixer
MetaMixer, MetaWinnerEnsemble
)

all_strategies.extend([MetaHunter, MetaMajority, MetaMinority, MetaWinner,
MetaMajorityMemoryOne, MetaWinnerMemoryOne,
MetaMajorityFiniteMemory, MetaWinnerFiniteMemory,
MetaMajorityLongMemory, MetaWinnerLongMemory, MetaMixer])
MetaMajorityLongMemory, MetaWinnerLongMemory, MetaMixer,
MetaWinnerEnsemble])


# Distinguished strategy collections in addition to
Expand Down
43 changes: 39 additions & 4 deletions axelrod/strategies/meta.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from axelrod import Actions, Player, obey_axelrod
from axelrod import Actions, Player, obey_axelrod, random_choice
from ._strategies import all_strategies
from .hunter import (
DefectorHunter, AlternatorHunter, RandomHunter, MathConstantHunter,
Expand Down Expand Up @@ -54,7 +54,8 @@ def strategy(self, opponent):
# Get the results of all our players.
results = [player.strategy(opponent) for player in self.team]

# A subclass should just define a way to choose the result based on team results.
# A subclass should just define a way to choose the result based on
# team results.
return self.meta_strategy(results, opponent)

def meta_strategy(self, results, opponent):
Expand Down Expand Up @@ -137,7 +138,8 @@ def __init__(self, team=None):
t.score = 0

def strategy(self, opponent):
# Update the running score for each player, before determining the next move.
# Update the running score for each player, before determining the
# next move.
if len(self.history):
for player in self.team:
game = self.match_attributes["game"]
Expand All @@ -149,7 +151,8 @@ def strategy(self, opponent):
def meta_strategy(self, results, opponent):
scores = [pl.score for pl in self.team]
bestscore = max(scores)
beststrategies = [i for i, pl in enumerate(self.team) if pl.score == bestscore]
beststrategies = [i for (i, pl) in enumerate(self.team)
if pl.score == bestscore]
bestproposals = [results[i] for i in beststrategies]
bestresult = C if C in bestproposals else D

Expand All @@ -165,6 +168,38 @@ def meta_strategy(self, results, opponent):

return bestresult

class MetaWinnerEnsemble(MetaWinner):
"""A variant of MetaWinner that chooses one of the top scoring strategies
at random against each opponent.
Names:
Meta Winner Ensemble: Original name by Marc Harper
"""

name = "Meta Winner Ensemble"

def meta_strategy(self, results, opponent):
# Sort by score
scores = [(pl.score, i) for (i, pl) in enumerate(self.team)]
# Choose one of the best scorers at random
scores.sort(reverse=True)
prop = max(1, int(len(scores) * 0.08))
index = choice([i for (s, i) in scores[:prop]])

# Update each player's proposed history with his proposed result, but
# always after the new result has been settled based on scores
# accumulated until now.
for r, t in zip(results, self.team):
t.proposed_history.append(r)

if opponent.defections == 0:
# Don't poke the bear
return C

# return result
return results[index]


class MetaHunter(MetaPlayer):
"""A player who uses a selection of hunters."""
Expand Down
30 changes: 21 additions & 9 deletions axelrod/tests/unit/test_classification.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ def test_is_basic(self):
self.assertFalse(axelrod.is_basic(strategy()), msg=strategy)


def str_reps(xs):
"""Maps a collection of player classes to their string representations."""
return set(map(str, [x() for x in xs]))


class TestStrategies(unittest.TestCase):

def test_strategy_list(self):
Expand Down Expand Up @@ -159,39 +164,46 @@ def test_inclusion_of_strategy_lists(self):
axelrod.strategies,
axelrod.ordinary_strategies,
axelrod.cheating_strategies]:
self.assertTrue(set(strategy_list).issubset(all_strategies_set))
self.assertTrue(str_reps(strategy_list).issubset(
str_reps(all_strategies_set)))

strategies_set = set(axelrod.strategies)
for strategy_list in [axelrod.demo_strategies,
axelrod.basic_strategies,
axelrod.long_run_time_strategies]:
self.assertTrue(set(strategy_list).issubset(strategies_set))
self.assertTrue(str_reps(strategy_list).issubset(
str_reps(strategies_set)))

def test_long_run_strategies(self):
long_run_time_strategies = [axelrod.MetaMajority,
axelrod.MetaMinority,
axelrod.MetaWinner,
axelrod.MetaWinnerEnsemble,
axelrod.MetaMajorityMemoryOne,
axelrod.MetaWinnerMemoryOne,
axelrod.MetaMajorityFiniteMemory,
axelrod.MetaWinnerFiniteMemory,
axelrod.MetaMajorityLongMemory,
axelrod.MetaWinnerLongMemory,
axelrod.MetaMixer]
self.assertTrue(long_run_time_strategies,
axelrod.long_run_time_strategies)

self.assertEqual(str_reps(long_run_time_strategies),
str_reps(axelrod.long_run_time_strategies))

def test_meta_inclusion(self):
self.assertTrue(axelrod.MetaMajority in axelrod.strategies)
self.assertTrue(str(axelrod.MetaMajority()) in
str_reps(axelrod.strategies))

self.assertTrue(axelrod.MetaHunter in axelrod.strategies)
self.assertFalse(
axelrod.MetaHunter in axelrod.long_run_time_strategies)
self.assertTrue(str(axelrod.MetaHunter()) in
str_reps(axelrod.strategies))
self.assertFalse(str(axelrod.MetaHunter()) in
str_reps(axelrod.long_run_time_strategies))

def test_demo_strategies(self):
demo_strategies = [axelrod.Cooperator,
axelrod.Defector,
axelrod.TitForTat,
axelrod.Grudger,
axelrod.Random]
self.assertTrue(demo_strategies, axelrod.demo_strategies)
self.assertTrue(str_reps(demo_strategies),
str_reps(axelrod.demo_strategies))
37 changes: 34 additions & 3 deletions axelrod/tests/unit/test_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import axelrod
import copy

from .test_player import TestPlayer
from .test_player import TestPlayer, test_responses

C, D = axelrod.Actions.C, axelrod.Actions.D

Expand Down Expand Up @@ -153,7 +153,8 @@ def test_strategy(self):
P1 = axelrod.MetaWinner(team=[axelrod.Cooperator, axelrod.Defector])
P2 = axelrod.Player()

# This meta player will simply choose the strategy with the highest current score.
# This meta player will simply choose the strategy with the highest
# current score.
P1.team[0].score = 0
P1.team[1].score = 1
self.assertEqual(P1.strategy(P2), C)
Expand Down Expand Up @@ -185,6 +186,35 @@ def test_strategy(self):
self.assertEqual(player.history[-1], D)


class TestMetaWinnerEnsemble(TestMetaPlayer):
name = "Meta Winner Ensemble"
player = axelrod.MetaWinnerEnsemble
expected_classifier = {
'memory_depth': float('inf'), # Long memory
'stochastic': True,
'makes_use_of': set(['game']),
'long_run_time': True,
'inspects_source': False,
'manipulates_source': False,
'manipulates_state': False
}

expected_class_classifier = copy.copy(expected_classifier)
expected_class_classifier['stochastic'] = False
expected_class_classifier['makes_use_of'] = set([])

def test_strategy(self):
self.first_play_test(C)

P1 = axelrod.MetaWinner(team=[axelrod.Cooperator, axelrod.Defector])
P2 = axelrod.Cooperator()
test_responses(self, P1, P2, [C] * 4, [C] * 4, [C] * 4)

P1 = axelrod.MetaWinner(team=[axelrod.Cooperator, axelrod.Defector])
P2 = axelrod.Defector()
test_responses(self, P1, P2, [C] * 4, [D] * 4, [D] * 4)


class TestMetaHunter(TestMetaPlayer):

name = "Meta Hunter"
Expand All @@ -201,7 +231,8 @@ class TestMetaHunter(TestMetaPlayer):
def test_strategy(self):
self.first_play_test(C)

# We are not using the Cooperator Hunter here, so this should lead to cooperation.
# We are not using the Cooperator Hunter here, so this should lead to
# cooperation.
self.responses_test([C, C, C, C], [C, C, C, C], [C])

# After long histories tit-for-tat should come into play.
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials/advanced/classification_of_strategies.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ Some strategies have been classified as having a particularly long run time::
... }
>>> strategies = axl.filtered_strategies(filterset)
>>> len(strategies)
10
11

Strategies that :code:`manipulate_source`, :code:`manipulate_state`
and/or :code:`inspect_source` return :code:`False` for the :code:`obey_axelrod`
Expand Down

0 comments on commit 2ab2a9f

Please sign in to comment.