egttools.analytical.PairwiseComparison

class PairwiseComparison

Bases: pybind11_object

Analytical pairwise-comparison process for finite populations.

This class studies evolutionary dynamics in a well-mixed population of fixed size \(Z\), whose state is represented by a vector of strategy counts \(x = (x_1, \dots, x_n)\) satisfying \(\sum_{i=1}^n x_i = Z\).

Under the pairwise comparison rule, strategy updates are driven by pairwise imitation, typically through the Fermi kernel

\[p_{i \to j}(x) = \frac{1}{1 + \exp[-\beta (f_j(x) - f_i(x))]},\]

where \(\beta \ge 0\) is the intensity of selection and \(f_i(x)\) is the fitness of strategy \(i\) in state \(x\).

The class provides tools to construct the full Markov transition matrix, compute gradients of selection, fixation probabilities, and the reduced small-mutation-limit (SML) dynamics.

Construct an analytical pairwise-comparison process.

Parameters:
  • population_size (int) – Population size \(Z\).

  • game (egttools.games.AbstractGame) – Game defining the fitness of each strategy as a function of the current population state.

Notes

The number of population states is

\[|\mathcal{S}| = \binom{Z + n - 1}{n - 1},\]

where \(n\) is the number of strategies.

Construct an analytical pairwise-comparison process with a configurable fitness cache.

Parameters:
  • population_size (int) – Population size \(Z\).

  • game (egttools.games.AbstractGame) – Game defining the fitness of each strategy as a function of the current population state.

  • cache_size (int) – Maximum number of cached fitness evaluations.

Methods

calculate_fixation_probability

Compute the fixation probability of one mutant in a monomorphic resident population.

calculate_gradient_of_selection

Compute the gradient of selection without mutation at a given population state.

calculate_gradient_of_selection_with_mutation

Compute the gradient of selection with mutation at a given population state.

calculate_log_fixation_probability

Return the natural log of the fixation probability, \(\log\rho\).

calculate_transition_and_fixation_matrix_sml

Return the reduced transition matrix and fixation matrix in the small-mutation limit.

calculate_transition_and_log_fixation_matrix_sml

Return a numerically stable SML transition matrix and the log-fixation matrix.

calculate_transition_matrix

Compute the full transition matrix of the finite-population Markov chain.

game

Return the underlying game.

nb_states

Return the total number of population states.

nb_strategies

Return the number of strategies.

population_size

Return the population size.

pre_calculate_edge_fitnesses

Precompute fitness values along all edges of the simplex.

update_population_size

Update the population size.

__init__()

Construct an analytical pairwise-comparison process.

Parameters:
  • population_size (int) – Population size \(Z\).

  • game (egttools.games.AbstractGame) – Game defining the fitness of each strategy as a function of the current population state.

Notes

The number of population states is

\[|\mathcal{S}| = \binom{Z + n - 1}{n - 1},\]

where \(n\) is the number of strategies.

Construct an analytical pairwise-comparison process with a configurable fitness cache.

Parameters:
  • population_size (int) – Population size \(Z\).

  • game (egttools.games.AbstractGame) – Game defining the fitness of each strategy as a function of the current population state.

  • cache_size (int) – Maximum number of cached fitness evaluations.

__new__(**kwargs)
calculate_fixation_probability()

Compute the fixation probability of one mutant in a monomorphic resident population.

This method restricts the dynamics to the one-dimensional edge involving the invading and resident strategies only. It returns the probability that a single invader eventually takes over the whole population.

Parameters:
  • invading_strategy_index (int) – Index of the invading strategy.

  • resident_strategy_index (int) – Index of the resident strategy.

  • beta (float) – Intensity of selection \(\beta\).

  • high_precision (bool, optional) – When True and the library was compiled with Boost multiprecision support, the internal streaming log-sum-exp is evaluated in 50-digit decimal arithmetic, extending the representable range of \(\exp(\log\phi)\). The return value is still a float; values below DBL_MIN (~2.2e-308) are rounded to zero regardless. Use calculate_log_fixation_probability() for the full dynamic range. Defaults to False.

Returns:

Probability that one invader fixates in a population of residents.

Return type:

float

calculate_gradient_of_selection()

Compute the gradient of selection without mutation at a given population state.

Let \(x = (x_1,\dots,x_n)\) be the current state. This method returns the expected one-step drift induced only by selection. For each strategy \(i\),

\[g_i(x) = \frac{1}{n} \sum_{j \ne i} \left[ T^{\mathrm{sel}}_{j \to i}(x) - T^{\mathrm{sel}}_{i \to j}(x) \right],\]

where \(T^{\mathrm{sel}}_{j \to i}(x)\) is the probability that one individual of strategy \(j\) is replaced by one individual of strategy \(i\) under pairwise comparison alone.

Under the Fermi rule, the local net flux can be written as

\[T^{\mathrm{sel}}_{j \to i}(x) - T^{\mathrm{sel}}_{i \to j}(x) = \frac{x_i x_j}{Z(Z-1)} \tanh\!\left(\frac{\beta}{2}(f_i(x)-f_j(x))\right).\]

The resulting vector is tangent to the simplex, so

\[\sum_{i=1}^n g_i(x) = 0.\]
Parameters:
  • beta (float) – Intensity of selection \(\beta\).

  • state (numpy.ndarray) – One-dimensional integer array of shape (nb_strategies,) containing the current population state.

Returns:

One-dimensional array of shape (nb_strategies,) containing the mutation-free gradient of selection.

Return type:

numpy.ndarray

calculate_gradient_of_selection_with_mutation()

Compute the gradient of selection with mutation at a given population state.

Let \(x = (x_1,\dots,x_n)\) be the current state, with population size \(Z\) and \(n\) strategies. This method returns the expected one-step drift when both pairwise comparison and mutation are active:

\[g_i^{(\mu)}(x) = (1-\mu)\, g_i(x) + \frac{\mu_{\mathrm{eff}}}{nZ}\left(Z - n x_i\right),\]

where \(g_i(x)\) is the mutation-free gradient returned by calculate_gradient_of_selection(), and \(\mu_{\mathrm{eff}}\) is the effective mutation probability towards one specific alternative strategy:

\[\begin{split}\mu_{\mathrm{eff}} = \begin{cases} \mu, & n = 2, \\ \mu/(n-1), & n > 2. \end{cases}\end{split}\]

The first term is the selection contribution scaled by \((1-\mu)\), and the second term is the mutation drift induced by uniform mutation towards the other strategies.

As in the mutation-free case, the resulting vector is tangent to the simplex:

\[\sum_{i=1}^n g_i^{(\mu)}(x) = 0.\]
Parameters:
  • beta (float) – Intensity of selection \(\beta\).

  • mu (float) – Mutation probability \(\mu\).

  • state (numpy.ndarray) – One-dimensional integer array of shape (nb_strategies,) containing the current population state.

Returns:

One-dimensional array of shape (nb_strategies,) containing the gradient with mutation.

Return type:

numpy.ndarray

calculate_log_fixation_probability()

Return the natural log of the fixation probability, \(\log\rho\).

Unlike calculate_fixation_probability(), this method never underflows: the result is a finite float for any combination of \(\beta\), population size, and fitness values. It is computed as \(-\mathrm{softplus}(\log\phi)\), so values as small as \(e^{-10^{308}}\) are representable.

Parameters:
  • invading_strategy_index (int) – Index of the invading strategy.

  • resident_strategy_index (int) – Index of the resident strategy.

  • beta (float) – Intensity of selection \(\beta\).

Returns:

\(\log\rho(\text{invader} \to \text{resident})\), always finite.

Return type:

float

calculate_transition_and_fixation_matrix_sml()

Return the reduced transition matrix and fixation matrix in the small-mutation limit.

In the Small Mutation Limit (SML), mutations are assumed sufficiently rare that the population is almost always monomorphic before the next mutation occurs. The resulting reduced Markov chain acts only on the monomorphic states.

If the current monomorphic state is strategy \(i\), then for \(i \ne j\)

\[T_{ij}^{\mathrm{SML}} = \frac{\rho_{ij}}{n-1},\]

where \(\rho_{ij}\) is the fixation probability of one mutant of strategy \(j\) in a resident population of strategy \(i\). The diagonal entries are set so that each row sums to one.

Parameters:

beta (float) – Intensity of selection \(\beta\).

Returns:

A tuple (transition_matrix, fixation_probabilities) where:

  • transition_matrix is the reduced SML transition matrix of shape (nb_strategies, nb_strategies);

  • fixation_probabilities[i, j] is the probability that one mutant of strategy j fixates in a population of strategy i.

Return type:

tuple[numpy.ndarray, numpy.ndarray]

calculate_transition_and_log_fixation_matrix_sml()

Return a numerically stable SML transition matrix and the log-fixation matrix.

Like calculate_transition_and_fixation_matrix_sml() but stores \(\log\rho_{ij}\) and builds the transition matrix by scaling all off-diagonal entries by \(\exp(-\max_{k\ne l}\log\rho_{kl})\). This global rescaling preserves the stationary distribution while guaranteeing a valid stochastic matrix even when every \(\rho_{ij}\) underflows to zero in double precision.

Parameters:

beta (float) – Intensity of selection \(\beta\).

Returns:

A tuple (transition_matrix, log_fixation_probabilities) where:

  • transition_matrix has the same stationary distribution as the standard SML matrix but is numerically well-conditioned;

  • log_fixation_probabilities[i, j] = \(\log\rho_{ij}\).

Return type:

tuple[numpy.ndarray, numpy.ndarray]

calculate_transition_matrix()

Compute the full transition matrix of the finite-population Markov chain.

The chain evolves on the set of all population states

\[\mathcal{S} = \left\{x \in \mathbb{N}^n : \sum_{i=1}^n x_i = Z \right\}.\]

Each off-diagonal transition changes the state by replacing one individual of one strategy by one individual of another strategy. Mutation is incorporated directly into the transition probabilities, and diagonal entries are set so that each row sums to one.

Parameters:
  • beta (float) – Intensity of selection \(\beta\).

  • mu (float) – Mutation probability \(\mu\).

Returns:

Sparse transition matrix of shape (nb_states, nb_states).

Return type:

scipy.sparse.csr_matrix

Notes

For large state spaces, explicitly constructing this matrix may require a large amount of memory.

Implementation note: fitness values are pre-computed in a serial pass (with the GIL held, so Python-subclassed games work correctly), then the sparse matrix is assembled in a parallel OpenMP pass (with the GIL released).

game()

Return the underlying game.

Returns:

Reference to the game used to evaluate fitness.

Return type:

egttools.games.AbstractGame

nb_states()

Return the total number of population states.

Returns:

Number of states in the full Markov chain.

Return type:

int

nb_strategies()

Return the number of strategies.

Returns:

Number of strategies.

Return type:

int

population_size()

Return the population size.

Returns:

Population size \(Z\).

Return type:

int

pre_calculate_edge_fitnesses()

Precompute fitness values along all edges of the simplex.

This is particularly useful for repeated pairwise fixation calculations, since fixation probabilities only depend on states involving two strategies at a time.

update_population_size()

Update the population size.

Parameters:

population_size (int) – New population size \(Z\).

__annotations__ = {}