Ludics

2026-04-10

My PhD student Harry and I have been working on a Python library for modelling population dynamics. This is part of his wider research with Sebastian Kraphol to model behaviour related to international climate agreements.

The library is called ludics.

A design decision that sits at the heart of ludics is that players are heterogeneous by default. Each player has their own type and their own payoff, and the fitness function receives the full ordered population state rather than just a summary statistic. This is in contrast to most published models of evolutionary dynamics, which assume a homogeneous population where every player of a given type is identical. That assumption makes the mathematics tidier but rules out the kind of variation between individuals that Harry and Sebastian's climate work actually needs. The homogeneous case is still available as a special case, but it is not the default framing.

The generic structure is: you define a fitness function that maps a population state to a payoff for each player, then pass it to one of the library's evolutionary dynamics to get transition probabilities, from which the full Markov chain transition matrix is assembled.

population state (heterogeneous players, each with type + payoff)
        |
        v
  fitness function
        |
        v
  evolutionary dynamic  <-- Moran / Fermi / introspection /
        |                    aspiration
        v
  transition matrix
       / \
      /   \
  exact   simulate
  (small   (large
    N)       N)

Here is a minimal example using the Moran process with a homogeneous public goods game:

import ludics
import ludics.fitness_functions
import numpy as np

state_space = ludics.get_state_space(N=2, k=2)

transition_matrix = ludics.generate_transition_matrix(
    state_space=state_space,
    compute_transition_probability=ludics.compute_moran_transition_probability,
    fitness_function=ludics.fitness_functions.homogeneous_pgg_fitness_function,
    selection_intensity=0.5,
    alpha=2,
    r=1.5,
)

ludics.approximate_absorption_matrix(transition_matrix)

The library supports four dynamics in total (Moran, Fermi imitation, introspection, and aspiration, covers both absorbing and ergodic chains, and can work symbolically via SymPy as well as numerically, which is useful when you want exact closed-form expressions for fixation probabilities.

For large populations the state space grows as \(k^N\), which makes exact computation of the transition matrix impractical. In that case ludics can forward-simulate the chain instead. It does this without having to enumerate the state space: it explores it as it goes.

It is pip installable from pypi, the code is on GitHub and the documentation, including a tutorial, how-to guides, and reference pages for each function, is at hefos.github.io/ludics.