Exploring Evolution through ABM

We’ve all heard of Darwin’s theory of evolution. Species slowly change in their physical characteristics in response to their environment, and the modifications (mutations) that offer an edge are the ones that survive. This phenomenon goes by the name of natural selection, or survival of the fittest. Wouldn’t it be great to see this happen before our eyes? Sadly, natural evolution often takes centuries and humans don’t stick around long enough to see it happen. What we can do, however, is simulate this. How? Through agent-based modelling of course!

So, what is Agent-based modelling?

Why not just formulate this mathematically, then? Well, it’s often easier to lay down rules for how agents should behave, rather than formulate sensible relationships between an arbitrary set of variables that define our model. This becomes even more relevant when there are a relatively large number of such variables.

Alright. How to go about making an ABM then?

Julia is a high-level, high-performance, dynamic programming language.

What this means, is that Julia is easy to write, like Python, and runs fast, unlike Python. It’s also particularly geared towards number crunching and scientific applications. Indeed, Julia has an exceedingly advanced scientific machine learning and numerical modelling framework, courtesy of the code-magicians at SciML.

For my purposes, however, I turned to Agents.jl, a pure-Julia library for agent-based modelling. It has an extensive set of features, an easy-to-use API and is exceedingly fast.

So what does my model do?

Each bacterium has the following properties:

  • age : How old the bacterium is.
  • energy : The current amount of energy a bacterium has.
  • sensory_radius : How far away the bacterium can spot food.
  • reproduction_threshold : How much energy a bacterium needs to reproduce through binary fission.
  • speed : How fast the bacterium can move, measured as the maximum number of steps it can take in any direction. The amount a bacterium eats in one iteration is also proportional to this parameter.
  • food_target : The location of the food source that a bacterium is moving toward.

The food is a regenerating resource at each grid tile. A tile with no food is an “empty tile”, and does not regenerate food. Any cell with non-zero food is a “food tile” and regenerates its food every iteration, up to a parameterised cap. The growth of food can be thought of like a fungus. The food tiles can spread to neighbouring empty tiles, causing them to start regenerating food. The probability for this spread is proportional to the amount of food the food tile has. This is like how a fungus grows around itself. Any empty tile can also become a food tile with a small, random probability. Think of this like a fungus spreading spores far away.

The rules governing the behaviour of the bacteria are simple:

  • If a bacterium grows too old (as determined by a global cap on lifetime), or runs out of energy, it dies.
  • If it has energy above its reproduction_threshold, it reproduces through binary fission. In this process, each child inherits the parent’s sensory_radius, reproduction_threshold and speed with some genetic variation. Additionally, the energy of the parent is distributed between the two children, after removing a fixed cost of reproduction.
  • If the bacterium is currently on a cell with some food, it will eat some food and gain energy. The amount of food it eats is proportional to its speed.
  • The bacterium will look for food. If it sees food, it will move toward it. Otherwise, it will move around randomly.
  • Each iteration, if not eating, the bacterium loses some energy proportional to its sensory radius and distance moved that iteration

The expectation is that the bacteria will show random variation in their parameters over time, and the more successful combinations will survive. This would demonstrate the process of natural selection.

What happens when the model runs?

Heatmap showing distribution of food on a 100 x 100 grid

There are four relatively small but concentrated spots of food, and one large area with less food per tile.

I configured the model to start with 10 identical bacteria distributed randomly on the map, and ran the simulation. The exact configuration corresponds to the config.bson config file, run using the runconfig function. Data from the simulation is constantly logged, and the following two visualisations capture a lot of what’s going on:

First, I made a video of how the food distribution changes over time.

This video shows 250 steps of a simulation that I run for 10,000 steps. The pattern continues for the rest of the duration. What’s interesting is how the bacteria finish the initial food distribution rapidly, and for the rest of the simulation it appears almost like ripples repeatedly sweeping across the grid, eating everything. The bacteria eat all the food faster than it regenerates. This is supported by the following plot:

The topmost plot is variation of population during the simulation. Below that, plots show the variation of sensory radius, reproduction threshold and speed of the bacteria with time. The line is the average value for each parameter, and around it is the standard deviation band.

The population spikes at first. This is due to the large amount of food available initially. Once the food is finished, the population drops steeply. After that, the population seems to oscillate, from nearly extinct to around 1000 bacteria. This corresponds to the “ripples” observed in the food distribution animation. Every time a ripple occurs, the bacteria eat a lot of food, thus reproducing more. Once the food finishes, a lot of the bacteria die due to starvation. As the number of bacteria decrease, the food demand decreases and the amount of food increases. Hence, the cycle continues.

It’s interesting to note that the average speed value is somewhat in-sync with the population. During a population boom, bacteria with higher speed survive. This can be explained by the fact that population rises when food is abundant, and bacteria are rewarded for being able to get to food faster and eat more. Once food becomes scarce, the bacteria that consume less energy roaming around looking for food are more likely to survive.

Reproduction threshold increases fairly steadily over time. This implies that over time it becomes more difficult to reproduce, but the two child bacteria have more energy to start off with. This is interesting, since it means that despite periodic and frequent “famines”, it’s still more advantageous to wait longer and eat more. This could be due to the fact that children that start with less energy also don’t survive long enough to get food. Another possibility is that the bacteria reach their threshold long before they become too old, so it’s beneficial to exploit the longer lifetime further.

Sensory radius has a less steady, yet still marked growth during the course of the simulation. A larger sensory radius allows bacteria to see food further away, at the expense of a greater energy cost each iteration. Consequently, bacteria that can see further can take more advantage of their higher speed. What’s more interesting to me, however, is that the sensory radius appears to settle at around 6 for most of the simulation, and only increases near the end. I’m yet to explain this, and would love inputs.

My thoughts

A great deal of thought went into figuring out how energy should be gained and expended, and I still think there’s a more appropriate alternative I haven’t yet arrived at. Additionally, I tried adding multiple species of bacteria, each of which start off with a different combination of parameters. Every time, all but one species died off within a few hundred iterations, and the other species behaved similarly to the above analysis. It would’ve been nice to see coexistence, if it’s even possible through this model.

All of the code for this simulation, including data collection and plotting, is available in this GitHub repository. If anyone makes any interesting modifications, or finds a configuration that leads to interesting results, I’d love to see the results. If you have any suggestions, drop a comment below!

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store