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:

  • ApologizingTransformer: 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)
    >>> player = DeadlockBreakingTFT()
    
  • 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()
    
  • RetaliateTransformer(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.