I just coded a simple simulation module that looks at the evolution of a continuous trait in a haploid asexually reproducing population under density dependent competition in discrete time (i.e. non-overlapping generations, using recurrence equations). What I am interested in is finding out whether evolution would always favour selecting for increased intrinsic growth rates R, perhaps eventually pushing the population to go extinct (a scenario known as "evolutionary suicide"), or if instead there would be selection for restrained growth rates, e.g. due to the fact that more selfish lineages with higher intrinsic growth rates would more often move into the chaotic regime and go extinct faster.
The module I have takes as arguments the desired fitness function (e.g. the discrete logistic model), the initial trait values of your individuals in the 1st generation, the mutation rate and the standard deviation of the normal deviation that is applied to mutants and the nr of generations to run the simulation :
EvolveHapl[fitnessfunc_, initpop_, mutrate_, stdev_, generations_] :=
Module[{ndist, traitvalues, currpopsize, fastPoisson, fitnessinds,
numberoffspring, nrmutants, rnoise, rndelem},
ndist = NormalDistribution[0, stdev] ;
traitvalues = Table[{}, {generations + 1}]; (*
list of lists containing ind trait values in each generation *)
traitvalues[[1]] = initpop;
currpopsize = Length[traitvalues[[1]]];
(* fast Poisson random number generator *)
fastPoisson =
Compile[{{\[Lambda], _Real}},
Module[{b = 1., i, a = Exp[-\[Lambda]]},
For[i = 0, b >= a, i++, b *= RandomReal[]];
i - 1], RuntimeAttributes -> {Listable},
Parallelization -> True];
Do[fitnessinds =
Table[fitnessfunc[traitvalues[[gen - 1]][[i]], currpopsize], {i,
1, currpopsize}]; (*
fitness of every individual in the population,
in mean number of offspring *)
numberoffspring = fastPoisson[fitnessinds]; (*
absolute number of offspring that every individual produces *)
traitvalues[[gen]] =
Flatten[Table[
Table[traitvalues[[gen - 1]][[i]], {j, 1,
numberoffspring[[i]]}], {i, 1, currpopsize}]]; (*
expected offspring trait values before mutation *)
currpopsize = Length[traitvalues[[gen]]]; (*
new population size *)
nrmutants =
RandomVariate[BinomialDistribution[currpopsize, mutrate]]; (*
nr of offspring that should mutate *)
rnoise = RandomReal[ndist, nrmutants]; (*
noise to be added to the trait values of the mutants *)
Do[rndelem = RandomInteger[{1, currpopsize}];
traitvalues[[gen]][[rndelem]] =
Max[traitvalues[[gen]][[rndelem]] + rnoise[[i]], 0];, {i, 1,
nrmutants}];, (* mutate trait values *)
{gen, 2, generations + 1}];
Return[traitvalues ]];
And to plot the resulting list of individual trait values I use
PlotResult[traitvalues_] := Module[{},
generations = Length[traitvalues];
Print["Mean phenotype at the beginning : " <>
ToString[Mean[traitvalues[[1]]]]];
Print["Maximum mean phenotype at any generation : " <>
ToString[
Max[Table[
Mean[traitvalues[[i]]], {i, 1, generations}] /. {Mean[{}] ->
0}]]];
Print["Mean phenotype after " <> ToString[ngens] <>
" generations : " <>
ToString[
Mean[traitvalues[[generations]]] /. {Mean[{}] ->
"- (population extinct)"}]];
Print["Final population size : " <>
ToString[Length[traitvalues[[generations]]]]];
maxscaleN =
Max[Table[{Length[traitvalues[[i]]]}, {i, 1, generations}]];
minscaleN =
Min[Table[{Length[traitvalues[[i]]]}, {i, 1, generations}]];
maxscaleP = Max[Flatten[traitvalues]];
GraphicsRow[{Show[
ArrayPlot[
Table[BinCounts[
traitvalues[[i]], {0, maxscaleP + 0.5, 0.05}], {i, 1,
generations}]/
Table[Length[traitvalues[[i]]] + 0.00001, {i, 1, generations}],
DataRange -> {{0, maxscaleP + 0.5}, {1, generations}},
DataReversed -> True], Frame -> True,
FrameLabel -> {"Phenotype frequency", "Generation"},
FrameTicks -> True, AspectRatio -> 2],
ListPlot[
Table[{Length[traitvalues[[i]]], i}, {i, 1, generations}],
Joined -> True, Frame -> True,
FrameLabel -> {"Population size", "Generation"},
AspectRatio -> 2,
PlotRange -> {{Clip[minscaleN - 50, {0, \[Infinity]}],
maxscaleN + 50}, {0, generations}}]}]
]
Running it, however, is quite slow, e.g.
psize = 300; ngens = 5000; mutrate = 0.01; stdev = 0.05; K = 2*psize;
f[R_, popsize_] := Max[(1 + R *(1 - popsize/K)), 0.00001];
First@AbsoluteTiming[
traitvalues =
EvolveHapl[ f, RandomReal[{2.5, 2.6}, psize], mutrate, stdev,
ngens];]
PlotResult[traitvalues]
204.02
I was just wondering if there might be any obvious ways to speed up this routine in Mathematica? E.g. could I get rid of all my Do[]
and Table[]
loops somehow? Or could this whole routine or parts of it be compiled to speed it up? I've already replaced the Poisson number generation with a faster compiled version and used Max[]
rather than Clip[]
in my original post, but this hasn't improved speed as much as I had hoped it would... Or better to go to C or C++ for this kind of simple simulation?