diff --git a/docs/explanation/explanation_reference.md b/docs/explanation/explanation_reference.md index 9fde036..cc71539 100644 --- a/docs/explanation/explanation_reference.md +++ b/docs/explanation/explanation_reference.md @@ -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) diff --git a/docs/explanation/markov_chain.md b/docs/explanation/markov_chain.md index e1fc3d9..9300feb 100644 --- a/docs/explanation/markov_chain.md +++ b/docs/explanation/markov_chain.md @@ -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 @@ -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). \ No newline at end of file diff --git a/docs/explanation/state_representation.md b/docs/explanation/state_representation.md index 8a8ac55..fa7e7af 100644 --- a/docs/explanation/state_representation.md +++ b/docs/explanation/state_representation.md @@ -29,9 +29,10 @@ 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 @@ -39,4 +40,4 @@ 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. \ No newline at end of file diff --git a/docs/how-to/homogeneous_pop.md b/docs/how-to/homogeneous_pop.md new file mode 100644 index 0000000..cc7a37e --- /dev/null +++ b/docs/how-to/homogeneous_pop.md @@ -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): +... 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. \ No newline at end of file diff --git a/docs/how-to/how-to.md b/docs/how-to/how-to.md index 25b1eac..8ffce77 100644 --- a/docs/how-to/how-to.md +++ b/docs/how-to/how-to.md @@ -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)