SLiM
SLiM copied to clipboard
Allow for expressions in Eidos event times
I think a simple feature that could make Eidos a lot more extensible would be to allow for expressions in the generation that Eidos events are triggered. For example, (modifying an example from the manual):
initialize() {
defineConstant("N", 1000);
initializeMutationRate(1e-7);
initializeMutationType("m1", 0.5, "f", 0.0);
initializeGenomicElementType("g1", m1, 1.0);
initializeGenomicElement(g1, 0, 99999);
initializeRecombinationRate(1e-8);
}
1 { sim.addSubpop("p1", N); }
// start outputting after burnin, for twenty generations
(10*N):(10*N+20) late() { sim.outputFull(); }
10*N + 100 { sim.simulationFinished();
}
Overall, this would allow for burnins that are dependent on N, which is what the manual recommends and allow N to be varied across runs easily. I can imagine allowing for any arbitrary expression might slow things down too much, so perhaps an expression of constants only that's run at initialization would suffice.
Yes, this sort of thing would be useful. There are two problems. One, script blocks get parsed and set up in the scheduler before anything gets executed, so the value of N is not known at the time that the expression would be encountered. And two, "constants" are not really constant; N can be removed with rm() and re-defined with a different value, so the expressions involving N are not truly constant expressions. Those two difficulties have prevented me from doing anything with this sort of idea, until such time as I come with a solution. However, rescheduleScriptBlock() makes it pretty easy to achieve this sort of thing anyway, so it's mostly a matter of convenience; the functionality you want is fairly easily achieved, just with a less intuitive and pretty syntax than one might wish.
Rather than using rescheduleScriptBlock()
, which requires that an existing block be defined, I've been using registerLateEvent()
. So the example given would become:
initialize() {
defineConstant("N", 1000);
initializeMutationRate(1e-7);
initializeMutationType("m1", 0.5, "f", 0.0);
initializeGenomicElementType("g1", m1, 1.0);
initializeGenomicElement(g1, 0, 99999);
initializeRecombinationRate(1e-8);
}
1 {
sim.addSubpop("p1", N);
// start outputting after burnin, for twenty generations
sim.registerLateEvent(NULL, "{ sim.outputFull(); }", 10*N, 10*N+20);
}
For longer expressions, I create a function that is scheduled using registerLateEvent. The major disadvantage of this approach is that syntax errors in the lambda expressions don't have the appropriate source code line reported.
Rather than using
rescheduleScriptBlock()
, which requires that an existing block be defined, I've been usingregisterLateEvent()
. ... The major disadvantage of this approach is that syntax errors in the lambda expressions don't have the appropriate source code line reported.
Yes; with rescheduleScriptBlock()
errors are correctly attributed. Error attribution within lambdas and user-defined functions is something that could use some work in Eidos...
Well, it took a while, but it got fixed! Some architectural improvements were needed before it was practical to do. Any arbitrary expression is now allowed, except that variables can't be involved, only constants. Not clear what it would really mean to use variables anyway. :-> So you can do:
N+20 early() { ... }
and such, if N is a defined constant.
Thanks Ben! This looks super nice!
Amazing!