Hi Daiki,
Let's talk about this uniform versus Poisson distribution issue, because
I'm not sure what I think about it. In the model I posted, I used a
uniform distribution in this context:
reproduction() {
// assign birth randomly, at the birth rate
if (runif(1) < BIRTH_RATE)
subpop.addCrossed(individual, subpop.sampleIndividuals(1));
}
I guess this is what you're referring to. Here, my intention (perhaps
incorrect, as I will discuss below) was to use BIRTH_RATE as the
probability that a given individual will generate a new offspring; given
that intention, using a uniform distribution is correct. If BIRTH_RATE
is 0.001, for example, as in the model I posted, then I wanted the
focal individual for the reproduction() callback to generate an
offspring 0.1% of the time. But I think you are technically correct
that since the model is intended to be an approximation of a
continuous-time model, conceptually each reproduction event is
independent and the distribution of the number of offspring for a focal
individual, across one timestep, ought to be Poisson; for example, in a
given timestep, it ought to be possible for an individual to give birth
to more than one offspring (even if the probability of that, for a rate
like 0.0001, is extremely small).
In my defense, though, I don't think you would want to scale the
timesteps up to be large enough that this matters. The whole point is
to approximate continuous time by using very small timesteps, and if you
scale the model up too far you are effectively out of continuous time
and back in the world of discrete time. As long as you keep the
timescale of the model fine enough, my understanding (perhaps someone
better versed than I in math can confirm?) is that using runif() is
going to be essentially the same as using rpois(), I guess because of
https://en.wikipedia.org/wiki/Poisson_distribution#Law_of_rare_events.
And drawing from a uniform distribution is substantially faster than
drawing from a Poisson distribution, so I tend to take advantage of that
approximation when I can.
Note that the way death is handled is also, in effect, making this
approximation assumption:
early() {
// assign death randomly, at the death rate
inds = p1.individuals;
inds.fitnessScaling = 1 - DEATH_RATE;
}
For each individual, SLiM will do a uniform draw from [0,1] and compare
it to the fitnessScaling value, and if it is greater, the focal
individual will die; that is, conceptually, the same as what I did for
the birth code, using runif(). That is also technically wrong, for a
continuous-time model, I suppose; and it will also malfunction if the
timescale of the model is made too coarse.
I guess overall, my thinking is that since SLiM is not, in fact, a
continuous-time simulator and makes various assumptions in its design
that are based upon the discrete-time model, if you are going to try to
approximate a continuous-time model in SLiM by making timesteps
represent a very small time interval, you need to do that – keep your
timesteps small. In that regime, using runif() is a good approximation
of the Poisson (I think?) and will run faster (perhaps negligibly
faster, in this case, I don't know; I know that it makes a difference in
some models). Outside of that regime, probably a lot more will break
than just that approximation assumption; so don't do that. :->
But this is all debatable (assuming I am not simply wrong about the
math), and if you want to use rpois() instead I think that is fine. I
just don't think it should matter; if it does matter, then you might be
making your timesteps too large. But please, if I am mistaken about all
this, somebody correct me; I certainly don't want to be spreading
misinformation!
-----------------
Regarding recombination among haploids, that has a simpler answer: see
section 16.14 for an example. :->
Cheers,
-B.
Benjamin C. Haller
Messer Lab
Cornell University
Daiki Tagami wrote on 7/19/22 9:07 AM: