# Strategy Transformers¶

## What is a Strategy Transformer?¶

A strategy transformer is a function that modifies an existing strategy. For example, FlipTransformer takes a strategy and flips the actions from C to D and D to C:

>>> import axelrod as axl
>>> from axelrod.strategy_transformers import *
>>> FlippedCooperator = FlipTransformer()(axl.Cooperator)
>>> player = FlippedCooperator()
>>> opponent = axl.Cooperator()
>>> player.strategy(opponent)
D
>>> opponent.strategy(player)
C


Our player was switched from a Cooperator to a Defector when we applied the transformer. The transformer also changed the name of the class and player:

>>> player.name
'Flipped Cooperator'
>>> FlippedCooperator.name
'Flipped Cooperator'


This behavior can be suppressed by setting the name_prefix argument:

>>> FlippedCooperator = FlipTransformer(name_prefix=None)(axl.Cooperator)
>>> player = FlippedCooperator()
>>> player.name
'Cooperator'


Note carefully that the transformer returns a class, not an instance of a class. This means that you need to use the Transformed class as you would normally to create a new instance:

>>> from axelrod.strategy_transformers import NoisyTransformer
>>> player = NoisyTransformer(0.5)(axl.Cooperator)()


rather than NoisyTransformer(0.5)(axl.Cooperator()) or just NoisyTransformer(0.5)(axl.Cooperator).

## Included Transformers¶

The library includes the following transformers:

• ApologyTransformer: Apologizes after a round of (D, C):

 >>> ApologizingDefector = ApologyTransformer([D], [C])(axl.Defector)
>>> player = ApologizingDefector()

You can pass any two sequences in. In this example the player would apologize
after two consequtive rounds of (D, C)::

>>> ApologizingDefector = ApologyTransformer([D, D], [C, C])(axl.Defector)
>>> player = ApologizingDefector()

• DeadlockBreakingTransformer: Attempts to break (D, C) -> (C, D) deadlocks by cooperating:

>>> DeadlockBreakingTFT = DeadlockBreakingTransformer()(axl.TitForTat)

• DualTransformer: The Dual of a strategy will return the exact opposite set of moves to the original strategy when both are faced with the same history. [Ashlock2008]:

>>> DualWSLS = DualTransformer()(axl.WinStayLoseShift)
>>> player = DualWSLS()

• FlipTransformer: Flips all actions:

>>> FlippedCooperator = FlipTransformer()(axl.Cooperator)
>>> player = FlippedCooperator()

• FinalTransformer(seq=None): Ends the tournament with the moves in the sequence seq, if the tournament_length is known. For example, to obtain a cooperator that defects on the last two rounds:

>>> FinallyDefectingCooperator = FinalTransformer([D, D])(axl.Cooperator)
>>> player = FinallyDefectingCooperator()

• ForgiverTransformer(p): Flips defections with probability p:

>>> ForgivinDefector = ForgiverTransformer(0.1)(axl.Defector)
>>> player = ForgivinDefector()

• GrudgeTransformer(N): Defections unconditionally after more than N defections:

>>> GrudgingCooperator = GrudgeTransformer(2)(axl.Cooperator)
>>> player = GrudgingCooperator()

• InitialTransformer(seq=None): First plays the moves in the sequence seq, then plays as usual. For example, to obtain a defector that cooperates on the first two rounds:

>>> InitiallyCooperatingDefector = InitialTransformer([C, C])(axl.Defector)
>>> player = InitiallyCooperatingDefector()

• JossAnnTransformer(probability): Where probability = (x, y), the Joss-Ann of a strategy is a new strategy which has a probability x of choosing the move C, a probability y of choosing the move D, and otherwise uses the response appropriate to the original strategy. [Ashlock2008]:

>>> JossAnnTFT = JossAnnTransformer((0.2, 0.3))(axl.TitForTat)
>>> player = JossAnnTFT()

• MixedTransformer: Randomly plays a mutation to another strategy (or set of strategies. Here is the syntax to do this with a set of strategies:

>>> strategies = [axl.Grudger, axl.TitForTat]
>>> probability = [.2, .3]  # .5 chance of mutated to one of above
>>> player =  MixedTransformer(probability, strategies)(axl.Cooperator)


Here is the syntax when passing a single strategy:

>>> strategy = axl.Grudger
>>> probability = .2
>>> player =  MixedTransformer(probability, strategy)(axl.Cooperator)

• NiceTransformer(): Prevents a strategy from defecting if the opponent has not yet defected:

>>> NiceDefector = NiceTransformer()(axl.Defector)
>>> player = NiceDefector()

• NoisyTransformer(noise): Flips actions with probability noise:

>>> NoisyCooperator = NoisyTransformer(0.5)(axl.Cooperator)
>>> player = NoisyCooperator()

• RetaliationTransformer(N): Retaliation N times after a defection:

>>> TwoTitsForTat = RetaliationTransformer(2)(axl.Cooperator)
>>> player = TwoTitsForTat()

• RetaliateUntilApologyTransformer(): adds TitForTat-style retaliation:

>>> TFT = RetaliateUntilApologyTransformer()(axl.Cooperator)
>>> player = TFT()

• TrackHistoryTransformer: Tracks History internally in the Player instance in a variable _recorded_history. This allows a player to e.g. detect noise.:

>>> player = TrackHistoryTransformer()(axl.Random)()


## Composing Transformers¶

Transformers can be composed to form new composers, in two ways. You can simply chain together multiple transformers:

>>> cls1 = FinalTransformer([D,D])(InitialTransformer([D,D])(axl.Cooperator))
>>> p1 = cls1()


This defines a strategy that cooperates except on the first two and last two rounds. Alternatively, you can make a new class using compose_transformers:

>>> cls1 = compose_transformers(FinalTransformer([D, D]), InitialTransformer([D, D]))
>>> p1 = cls1(axl.Cooperator)()
>>> p2 = cls1(axl.Defector)()


## Usage as Class Decorators¶

Transformers can also be used to decorate existing strategies. For example, the strategy BackStabber defects on the last two rounds. We can encode this behavior with a transformer as a class decorator:

@FinalTransformer([D, D]) # End with two defections
class BackStabber(Player):
"""
Forgives the first 3 defections but on the fourth
will defect forever. Defects on the last 2 rounds unconditionally.
"""

name = 'BackStabber'
classifier = {
'memory_depth': float('inf'),
'stochastic': False,
'inspects_source': False,
'manipulates_source': False,
'manipulates_state': False
}

def strategy(self, opponent):
if not opponent.history:
return C
if opponent.defections > 3:
return D
return C


## Writing New Transformers¶

To make a new transformer, you need to define a strategy wrapping function with the following signature:

def strategy_wrapper(player, opponent, proposed_action, *args, **kwargs):
"""
Strategy wrapper functions should be of the following form.

Parameters
----------
player: Player object or subclass (self)
opponent: Player object or subclass
proposed_action: an axelrod.Action, C or D
The proposed action by the wrapped strategy
proposed_action = Player.strategy(...)
args, kwargs:
Any additional arguments that you need.

Returns
-------
action: an axelrod.Action, C or D

"""

# This example just passes through the proposed_action
return proposed_action


The proposed action will be the outcome of:

self.strategy(player)


in the underlying class (the one that is transformed). The strategy_wrapper still has full access to the player and the opponent objects and can have arguments.

To make a transformer from the strategy_wrapper function, use StrategyTransformerFactory, which has signature:

def StrategyTransformerFactory(strategy_wrapper, name_prefix=""):
"""Modify an existing strategy dynamically by wrapping the strategy
method with the argument strategy_wrapper.

Parameters
----------
strategy_wrapper: function
A function of the form strategy_wrapper(player, opponent, proposed_action, *args, **kwargs)
Can also use a class that implements
def __call__(self, player, opponent, action)
name_prefix: string, "Transformed "
A string to prepend to the strategy and class name
"""


So we use StrategyTransformerFactory with strategy_wrapper:

TransformedClass = StrategyTransformerFactory(generic_strategy_wrapper)
Cooperator2 = TransformedClass(*args, **kwargs)(axl.Cooperator)


If your wrapper requires no arguments, you can simply proceed as follows:

>>> TransformedClass = StrategyTransformerFactory(generic_strategy_wrapper)()
>>> Cooperator2 = TransformedClass(axl.Cooperator)


For more examples, see axelrod/strategy_transformers.py.