Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/explanation/explanation_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

Below is a list of further reading on various topics explored in `ludics`

- [How states are represented](state_representation.md)
- [Modelling evolutionary games as Markov chains](markov_chain.md)
- [What is a population dynamic?](population_dynamics.md)
- [The public goods game](public_goods_game.md)
39 changes: 38 additions & 1 deletion docs/explanation/markov_chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ An ergodic Markov chain is one which has no absorbing states, and so you are
able to traverse from any state to any other state in a finite number of steps.
In this case, we consider state distributions in the form

$$ \pi^t = (\pi^t_1, \pi^t_2, ...)$$
$$ \pi^t = (\pi^t_1, \pi^t_2, \dots)$$

Where $\pi^t_i$ is the proportion of the time spent in a certain state at step
$T$. We have that $\sum_{i=1}^N \pi^t_i = 1$. Now, if we multiply $\pi^t$ by
Expand All @@ -98,3 +98,40 @@ irreducible (all states can reach all other states in a finite number of steps)
and aperiodic (the states do not oscillate between certain subsets). Any
birth-death process fulfills these conditions, and thus reaches the steady
state in a finite number of steps.

### Homogeneous Birth-death chains

If we have a homogeneous population, states with $N_j$ players of each type are
indistinguishable. We can therefore collapse the state space into the set of
tuples:

$$ (N_1, N_2, N_3, \dots) $$

where two states are adjacent if they differ in exactly two positions,
increasing one by a single player and removing that player from the other.

In a two-action game with actions {$0,1$} we can represent this state space as the
numbers $0$ through $N$, where the state is represented by the number of players
using strategy $0$. In this case, we consider probabilities of "birth" and "death",
given by:

$$ \lambda_{i, i+1}, \lambda_{i,i-1} $$

The transition matrix of this process has the form:

$$ \begin{pmatrix}
1 & 0 & 0 & \dots & 0 & 0 & 0\\
\lambda_{1, 0} & 1 - \lambda_{1,0} - \lambda_{1,2} & \lambda_{1,2} & \dots & 0 & 0 & 0\\
0 & \lambda_{2,1} & 1 - \lambda_{2,1} - \lambda_{2,2} & \dots & 0 & 0 & 0\\
\vdots & \vdots & \vdots & \ddots & \vdots & \vdots & \vdots\\
0 & 0 & 0 & \dots & \lambda_{N-1, N-2} & 1-\lambda_{N-1, N-2}-\lambda_{N-1, N} & \lambda_{N-1, N}\\
0 & 0 & 0 & \dots & 0 & 0 & 1
\end{pmatrix}
$$

and the fixation probability of being absorbed into the state with $N$ players
using action $0$, after beginning with $i$ such players, is given by:

$$ \pi_i = \frac{1 + \sum_{j=1}^{i-1}\prod_{k=1}^j \frac{\lambda_{k,k-1}}{\lambda_{k,k+1}}}{1 + \sum_{j=1}^{i-1}\prod_{k=1}^{N-1} \frac{\lambda_{k,k-1}}{\lambda_{k,k+1}}} $$

For further reading, see chapters 6 and 7 of (Nowak, 2006).
9 changes: 5 additions & 4 deletions docs/explanation/state_representation.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ aspiration levels) and **asymmetric payoff functions** where the identity of
the player matters, not just the aggregate counts.

The cost is a larger state space. For symmetric games where only the count of
each strategy matters, many states are payoff-equivalent: `[0, 1, 0]` and
`[0, 0, 1]` produce the same fitness values under a homogeneous PGG. `ludics`
does not collapse these equivalent states; it works with the full $k^N$ space.
each strategy matters, many states are payoff-equivalent: `[0, 1, 0]` and
`[0, 0, 1]` produce the same fitness values under a homogeneous PGG. `ludics` does
not collapse these equivalent states by default; it works with the full $k^N$
space.

## Absorbing states

In games with two strategies, the two fully-homogeneous states (all-0 and
all-1) are typically absorbing under extrinsic dynamics (Moran, Fermi), since
there is no fitness difference to drive a change. Their absorption
probabilities are the **fixation probabilities** central to evolutionary game
theory.
theory.
72 changes: 72 additions & 0 deletions docs/how-to/homogeneous_pop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Model a Homogeneous Population

As a default, `ludics` models heterogeneous populations. However, many models
use a homogeneous population instead. We show the mathematics behind this in our
explanation of modelling evolutionary games as Markov chains.

The probability functions, fitness functions, and `generate_state_space`, are
built to handle a heterogeneous population. However,
`generate_transition_matrix` will accept a homogeneous population, provided one
writes a suitable probability function and fitness function.

Imagine we wish to model a 3 player homogeneous public goods game using the
Moran process. Then we can use the following model:
```py
>>> import ludics
>>> import numpy as np

>>> def homogeneous_pgg(number_of_cooperators, alpha, r, **kwargs):
... sum_contributions = r * number_of_cooperators * alpha
... contributor_return = sum_contributions - alpha
... return np.array([contributor_return, sum_contributions])

>>> def homogeneous_moran_process(source, target, fitness_function, N, epsilon, **kwargs):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a good name for this function right?

... source = source[0]
... target = target[0]
... difference = source - target
... if np.abs(difference) > 1:
... return 0
... elif np.abs(difference) == 0:
... return None
... fitness_C, fitness_D = fitness_function(number_of_cooperators=source, **kwargs)
... if difference == 1:
... return (1 / N) * ((N - source) * (1 + epsilon * fitness_D)) / (
... ((N - source) * (1 + epsilon * fitness_D))
... + (source * (1 + epsilon * fitness_C))
... )
... elif difference == -1:
... return (1 / N) * (
... (source * (1 + epsilon * fitness_C))
... / (
... ((N - source) * (1 + epsilon * fitness_D))
... + (source * (1 + epsilon * fitness_C))
... )
... )

>>> state_space = np.array([[0], [1], [2], [3]])
>>> alpha = 2
>>> r = 2
>>> epsilon = 0.1

>>> ludics.generate_transition_matrix(
... state_space=state_space,
... fitness_function=homogeneous_pgg,
... compute_transition_probability=homogeneous_moran_process,
... alpha=alpha,
... r=r,
... N=3,
... epsilon=epsilon,
... )
array([[1. , 0. , 0. , 0. ],
[0.23333333, 0.66666667, 0.1 , 0. ],
[0. , 0.12 , 0.66666667, 0.21333333],
[0. , 0. , 0. , 1. ]])

```

Note that while the above works for `generate_transition_matrix`, the function
`simulate_markov_chain` does not support being passed a homogeneous state space.
Also note that the state space *must* remain in the form
`np.array([[a],[b],...])`, or else the `generate_transition_matrix` function
will fail. Mutation is also not currently supported for this, but may be added
to the custom `compute_transition_probability` function.
1 change: 1 addition & 0 deletions docs/how-to/how-to.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ Below is a list of our How-to Guides
- [Choose the precision of a steady state calculation](steady_state_precision.md)
- [Simulating the Markov chain](simulate_markov_chain.md)
- [Model a public goods game](heterogeneous_pgg.md)
- [Model a homogeneous population](homogeneous_pop.md)
- [Use a symbolic fitness function for a two player game](symbolic_fitness.md)
- [Compare population dynamics](compare_dynamics.md)
Loading