Friday 27 May 2011

Spatial prisoner's dilemma: cooperation evolving on a grid

On this occasion, I will show how to prepare an interactive spatial prisoner's dilemma visualisation. Agents (one in each cell of a 2D grid) play the prisoner's dilemma game against each of their neighbours and then mimic the one they observed to be most successful, where, for simplicity, we consider only two types of players: cooperators and defectors.

To begin with, let us set up a nxn grid for the simulation, where the value of a cell is False when the player located there is a cooperator (all are cooperators by default). Also let offset be the general list of neighbours each player interacts with, in terms of their relative location with respect to the player. This is obtained as a list of all 2-Tuples of (-1,0,+1), for instance, (-1,0) is the cell directly above a player, where the 5-th tuple (0,0), representing the player herself, is Deleted.



This is then used to define a list of neighbours of a particular player, located in cell {rowcol}. To this end, I add the vectors in offset to {rowcol}, by applying the according function (&) to each element of offset via the Map (/@) operator, and then only select the Cases lying inside the grid (i.e. neighbours with coordinates between 1 and n, note the use of the conditional pattern operator /;).

I will now set up a couple of controls for the simulation, starting with an Animator that, once the play button is clicked, will keep cycling the update variable between 0 and 10 in unit steps at a default rate of once per second.

Next to it, there will be a Button that, when clicked, will generate a random state of the grid, with a proportion p of cooperators. This is done by first generating a nxn matrix of RandomReal numbers uniformly distributed between 0 and 1, and then mapping a function onto it that will change all numbers above p to True (defectors) and others to False (cooperators).

The value of p will be selected by user via a PopupMenu from the Range between 1/5 and 4/5. Finally, an additional variable T (representing gains from a non-cooperative deviation when playing a cooperator) will be set on a Slider between 1 and 2.

Combining these controls in a Row should look like this:

We will place the controls in a Panel, arranged in a Column with a DynamicModule that will actually run the simulation.

The above module comprises a LocatorPane, which captures clicks and saves their x and y coordinates  as point. Next, the cell to which this corresponds is identified and inverted (from False to True, or vice-versa) using the function defined below, allowing the user to manually edit the grid.

Conveniently, Mathematica gives the mouseclick coordinates in the range from 1 to n, but they are real numbers and must be transformed, e.g. mouseclick coordinates (0.3, 8.4) correspond to row 2, column 1 of a 10x10 grid. The resulting sequence of the grid row and column values are saved as coord and used to invert the associated cell via the Not (!) operator.

The remaining component of the DynamicModule above is gridE, the dynamically updated current image of the grid, defined as follows:

This is based on an ArrayPlot of the grid variable (converted to a matrix of zeroes and ones by Boole). Crucially, the grid is updated every time the update variable (cycled by the earlier Animator part of the controls) reaches the value 0. In such case, following a unit increase of update, the average payoffs achieved by every agent on the grid are saved as resultsE, where the resultsU procedure that computes them is specified as:



For every cell of the grid (as specified by the coordinates row, col) the status of the agent located there is saved as player, while the list of her neighbours, obtained via the earlier described neighboursU procedure, is saved as neighboursE
The status of every neighbour i on that list is then Extracted from grid and saved as rival, so that the payoff of player can be found accordingly via the nested If expression. More specifically, if rival is a defector (value of rival is True), then the payoff of player is 0. Otherwise (i.e. when playing a cooperator), it is equal to the value of the pre-set T variable if player is a defector herself, and if player is a cooperator too. 

The Mean of the Table listing these payoffs (from playing every neighbour) is then saved at position row, col of resultsU

Finally, the matrix of agents' mean payoffs, computed by resultsU above, is used by the gridU function below to re-set the grid variable in the earlier dynamic expression gridE.


Every agent's pair of row, col coordinates is here Joined (U) with the list of her neighbours and saved as neighboursE. The new value of the cell then depends on comparing the maximum payoff of neighbouring cooperators with the maximum of all neighbours.

To obtain the former, we first select all Cases of neigbouring coordinates x, y such that agents located there are cooperators (grid[[x,y]] is False, or !grid[[x,y]] is True). Next, we Extract the mean payoffs associated with each of those pairs of coordinates from resultsE, and take the Max of those payoffs.

The Maximum payoff over all neighbours is similarly obtained by using the list of all coordinates in neighboursE instead of only the selected Cases. The two are then compared and the cell at  rowcol is set to True when they are not equal (note the ! operator before the first Max).

Evaluating the earlier Panel expression now yields a ready-to-use interactive simulation, as shown in the video below.