Wright-Fisher model of evolution

This is one of the simplest models of population genetics that demonstrates the use of sample!. We implement a simple case of the model where we study haploids (cells with a single set of chromosomes) while for simplicity, focus only on one locus (a specific gene). In this example we will be dealing with a population of constant size.

It is also available from the Models module as Models.wright_fisher.

A neutral model

  • Imagine a population of n haploid individuals.
  • At each generation, n offsprings replace the parents.
  • Each offspring chooses a parent at random and inherits its genetic material.
using Agents
numagents = 100

Let's define an agent. The genetic value of an agent is a number (trait field).

mutable struct Haploid <: AbstractAgent
    id::Int
    trait::Float64
end

And make a model without any spatial structure:

model = ABM(Haploid)
AgentBasedModel with 0 agents of type Haploid
 no space
 scheduler: fastest

Create n random individuals:

for i in 1:numagents
    add_agent!(model, rand())
end

To create a new generation, we can use the sample! function. It chooses random individuals with replacement from the current individuals and updates the model. For example:

sample!(model, nagents(model))

The model can be run for many generations and we can collect the average trait value of the population. To do this we will use a model-step function (see step!) that utilizes sample!:

modelstep_neutral!(model::ABM) = sample!(model, nagents(model))

We can now run the model and collect data. We use dummystep for the agent-step function (as the agents perform no actions).

using Statistics: mean

data, _ = run!(model, dummystep, modelstep_neutral!, 20; adata = [(:trait, mean)])
data

21 rows × 2 columns

stepmean_trait
Int64Float64
100.519923
210.522981
320.534096
430.519882
540.48911
650.451904
760.472971
870.446395
980.459255
1090.472749
11100.463639
12110.417457
13120.428717
14130.399578
15140.43433
16150.443048
17160.430732
18170.429373
19180.429026
20190.437515
21200.370309

As expected, the average value of the "trait" remains around 0.5.

A model with selection

We can sample individuals according to their trait values, supposing that their fitness is correlated with their trait values.

model = ABM(Haploid)
for i in 1:numagents
    add_agent!(model, rand())
end

modelstep_selection!(model::ABM) = sample!(model, nagents(model), :trait)

data, _ = run!(model, dummystep, modelstep_selection!, 20; adata = [(:trait, mean)])
data

21 rows × 2 columns

stepmean_trait
Int64Float64
100.503069
210.664863
320.746255
430.776732
540.817812
650.869106
760.894729
870.897412
980.917239
1090.922672
11100.936745
12110.940743
13120.949065
14130.966485
15140.964019
16150.968136
17160.970103
18170.971051
19180.969316
20190.966822
21200.971415

Here we see that as time progresses, the trait becomes closer and closer to 1, which is expected - since agents with higher traits have higher probability of being sampled for the next "generation".