5.13.0 ITEM: 1. initialize() callbacks
Before a SLiM simulation can be run, the various classes underlying the simulation need to be set up with an initial configuration. Simulation configuration in SLiM is done in initialize() callbacks that run prior to the beginning of simulation execution. For our present purposes, the idea is very simple; in your input file, you can write something like this:
initialize()
{
...
}
The initialize() declaration specifies that the script block is to be executed as an initialize() callback before the simulation starts. The script between the braces {} would set up various aspects of the simulation by calling initialization functions. These are SLiM functions that may be called only in an initialize() callback, and their names begin with initialize to mark them clearly as such. You may also use other Eidos functionality in these callbacks; for example, you might automate generating a complex genetic structure containing many genes by using a for loop.
In general, it is required for a species to set up its genetic structure in an initialize() callback with calls to initializeMutationRate(), initializeRecombinationRate(), initializeMutationType(), initializeGenomicElementType(), and initializeGenomicElement(); species must call all of these, setting up at least one mutation type, at least one genomic element type, and at least one genomic element. The exception to this general rule is for species that have no genetics at all – species that are modeled purely on an ecological/behavioral level. Such species may be defined by calling none of those initialization functions; in this case, SLiM will default to a zero-length chromosome with mutation and recombination rates of zero. A middle ground between these two configuration paths is not allowed; either a species has no genetics, or it fully defines its genetics.
One thing worth mentioning is that in the context of an initialize() callback, the sim global representing the species being simulated is not defined. This is because the state of the simulation is not yet constructed fully, and accessing partially constructed state would not be safe. (Similarly, in multispecies models, the community object and the objects representing individual species are not yet defined.)
The above initialize() callback syntax implicitly declares a single species, with the default name of sim, and therefore sets up a single-species model. It is also possible to explicitly declare a species, which is done with this extended syntax (using a species name of fox as an example):
species fox initialize() { ... }
This sets up a multispecies model (although it might, in fact, declare only a single species, fox; the term “multispecies”, in SLiM parlance, really means “explicitly declared species”, but multispecies models almost always do contain multiple species, so the distinction is unimportant). In most respects multispecies models work identically to single-species models, so we will tend to focus on the single-species case in the reference documentation, with a species name of sim, for simplicity and clarity.
In single-species models all initialization can be done in a single initialize() callback (or you can have more than one, if you wish). In multispecies models, each species must be initialized with its own callback(s), as shown above. In addition, multispecies models also support an optional community-level initialization callback that is declared as follows:
species all initialize() { ... }
These callbacks, technically called non-species-specific initialize() callbacks, provide a place for community-level initialization to occur. They are run before any species-specific initialize() callbacks are run, so you might wish to set up all of your model parameters in one, providing a single location for all parameters. In multispecies models, the initializeModelType() and initializeInteractionType() functions may only be called from a non-species-specific initialize() callback, since those aspects of model configuration span the entire community. In single-species models, these functions may be called from an ordinary initialize() callback for simplicity and backward compatibility.
Once all initialize() callbacks have executed, in the order in which they are specified in the SLiM input file, the simulation will begin. The tick number at which it starts is determined by the Eidos events you have defined; the first tick in which an Eidos event is scheduled to execute is the tick at which the simulation starts. Similarly, the simulation will terminate after the last tick for which a script block (either an event or a callback) is registered to execute, unless the stop() function or the simulationFinished() method of Community or Species are called to end the simulation earlier.
5.13.1 ITEM: 2. Eidos events
An Eidos event is a block of Eidos code that is executed every tick, within a tick range, to perform a desired task. The syntax of an Eidos event declaration looks like one of these:
[id] [t1 [: t2]] first() { ... }
[id] [t1 [: t2]] early() { ... }
[id] [t1 [: t2]] late() { ... }
The first declaration declares a first() event that executes first thing in the tick cycle. The second declaration declares an early() event that executes relatively early in the tick cycle. The third declaration declares a late() event that executes near the end of the tick cycle. Exactly when these events run depends upon whether the model is a WF model or a nonWF model; see the tick cycle diagrams for those model types.
The id is an optional identifier like s1 (or more generally, sX, where X is an integer greater than or equal to 0) that defines an identifier that can be used to refer to the script block. In most situations it can be omitted, in which case the id is implicitly defined as -1, a placeholder value that essentially represents the lack of an identifier value. Supplying an id is only useful if you wish to manipulate your script blocks programmatically.
Then comes a tick or a range of ticks, and then a block of Eidos code enclosed in braces to form a compound statement. A trivial example might look like this:
1000:5000 early() {
catn(community.tick);
}
This would print the tick number in every tick in the specified range, which is obviously not very exciting. The broader point is that the Eidos code in the braces {} is executed early in every tick within the specified range of ticks. In this case, the tick range is 1000 to 5000, and so the Eidos event will be executed 4001 times (not 4000!). A range of ticks can be given, as in the example above, or a single tick can be given with a single integer:
100 late() {
print("Finished tick 100!");
}
The tick range may also be incompletely specified, with a somewhat idiosyncratic syntax. A range of 1000: would specify that the event should run in tick 1000 and every subsequent tick until the model finishes; a range of :1000 would similarly specify that the event should run in the first tick executed, and every subsequent tick, up to and including tick 1000.
In fact, you can omit specifying a tick altogether, in which case the Eidos event runs every tick. Since it takes a little time to set up the Eidos interpreter and interpret a script, it is advisable to use the narrowest range of ticks possible; however, that is more of a concern with callbacks, since they might be called many time in every tick, whereas first(), early(), and late() events will just be called once per tick.
The ticks specified for a Eidos event block can be any positive integer. All blocks that apply to a given time point will be run in definition order; blocks specified higher in the input file will run before those specified lower. Sometimes it is desirable to have a script block execute in a tick which is not fixed, but instead depends upon some parameter, defined constant, or calculation; this may be achieved by rescheduling the script block with the Community method rescheduleScriptBlock().
In multispecies models, one can optionally provide a ticks specifier before the definition of an Eidos event, specifying that the event should only run in ticks in which a particular species is active. That extended syntax looks like this:
[ticks species_name] [id] [t1 [: t2]] first() { ... }
[ticks species_name] [id] [t1 [: t2]] early() { ... }
[ticks species_name] [id] [t1 [: t2]] late() { ... }
The species_name should be the name of a species that was explicitly declared in the multispecies model. If the ticks specifier is omitted, the event will run in every tick (within the specified tick range).
When Eidos events are executed, several global variables are defined by SLiM for use by the Eidos code. Here is a summary of those SLiM globals:
community The Community object for the overall simulation
sim A Species object for the simulated species (in single-species simulations)
g1, ... GenomicElementType objects for defined genomic element types
i1, ... InteractionType objects for defined interaction types
m1, ... MutationType objects representing defined mutation types
p1, ... Subpopulation objects for existing subpopulations
s1, ... SLiMEidosBlock objects for named events and callbacks
self A SLiMEidosBlock object for the script block currently executing
In multispecies models, symbols for each species will be defined instead of sim. Note that species symbols such as sim are not available in initialize() callbacks, since the species objects have not yet been initialized. Similarly, the globals for subpopulations, mutation types, and genomic element types are only available after the point at which those objects have been defined by an initialize() callback.
5.13.2 ITEM: 3. mutationEffect() callbacks
An Eidos callback is a block of Eidos code that is called by SLiM in specific circumstances, to allow the customization of particular actions taken by SLiM while running the simulation of a species. Nine types of callbacks are presently supported (in addition to initialize() callbacks): mutationEffect() callbacks, discussed here, and fitnessEffect(), mateChoice(), modifyChild(), recombination(), interaction(), reproduction() , mutation(), and survival() callbacks.
A mutationEffect() callback is called by SLiM when it is determining the fitness effect of a mutation carried by an individual. Normally, the fitness effect of a mutation is determined by the selection coefficient s of the mutation and the dominance coefficient h of the mutation (the latter used only if the individual is heterozygous for the mutation). More specifically, the standard calculation for the fitness effect of a mutation takes one of two forms. If the individual is homozygous, then the fitness effect is (1+s), or:
w = w * (1.0 + selectionCoefficient),
where w is the relative fitness of the individual carrying the mutation. If the individual is heterozygous, then the dominance coefficient enters the picture, and the fitness effect is (1+hs) or:
w = w * (1.0 + dominanceCoeff * selectionCoeff).
The dominance coefficient usually comes from the dominanceCoeff property of the mutation’s MutationType; if the focal individual has only one non-null genome, however, such that the mutation is paired with a null genome (i.e., is actually hemizygous or haploid, not heterozygous), the haploidDominanceCoeff property of the MutationType is used instead.
That is the standard behavior of SLiM, reviewed here to provide a conceptual baseline. Supplying a mutationEffect() callback allows you to substitute any calculation you wish for the relative fitness effect of a mutation; the new relative fitness effect computation becomes:
w = w * mutationEffect()
where mutationEffect() is the value returned by your callback. This value is a multiplicative fitness effect, so 1.0 is neutral, unlike the selection coefficient scale where 0.0 is neutral; be careful with this distinction!
Like Eidos events, mutationEffect() callbacks are defined as script blocks in the input file, but they use a variation of the syntax for defining an Eidos event:
[id] [t1 [: t2]] mutationEffect(<mut-type-id> [, <subpop-id>]) { ... }
For example, if the callback were defined as:
1000:2000 mutationEffect(m2, p3) { 1.0; }
then a relative fitness of 1.0 (i.e. neutral) would be used for all mutations of mutation type m2 in subpopulation p3 from tick 1000 to tick 2000. The very same mutations, if also present in individuals in other subpopulations, would preserve their normal selection coefficient and dominance coefficient in those other subpopulations; this callback would therefore establish spatial heterogeneity in selection, in which mutation type m2 was neutral in subpopulation p3 but under selection in other subpopulations, for the range of ticks given.
In multispecies models, callbacks must be defined with a species specifier that states the species with which species the callback is associated. Such a definition looks like this:
species species_name [id] [t1 [: t2]] mutationEffect(...) { ... }
It is the same syntax, in other words, except for the species specifier at the beginning, with the name of the species that the callback will modify. As with the ticks specifier for events, this means the callback will only be called in ticks when the species is active; but the species specifier goes further, making that species the focal species for the callback.
In addition to the standard SLiM globals, a mutationEffect() callback is supplied with some additional information passed through “pseudo-parameters”, variables that are defined by SLiM within the context of the callback’s code to supply the callback with relevant information:
mut A Mutation object, the mutation whose relative fitness is being evaluated
homozygous A value of T (the mutation is homozygous), F (heterozygous), or NULL (it is
paired with a null chromosome, and is thus hemizygous or haploid)
effect The default relative fitness value calculated by SLiM
individual The individual carrying this mutation (an object of class Individual)
subpop The subpopulation in which that individual lives
These may be used in the mutationEffect() callback to compute a fitness value. To implement the standard fitness functions used by SLiM for an autosomal simulation with no null genomes involved, for example, you could do something like this:
mutationEffect(m1) {
if (homozygous)
return 1.0 + mut.selectionCoeff;
else
return 1.0 + mut.mutationType.dominanceCoeff * mut.selectionCoeff;
}
As mentioned above, a relative fitness of 1.0 is neutral (whereas a selection coefficient of 0.0 is neutral); the 1.0 + in these calculations converts between the selection coefficient scale and the relative fitness scale, and is therefore essential. However, the effect global variable mentioned above would already contain this value, precomputed by SLiM, so you could simply return effect to get that behavior when you want it:
mutationEffect(m1) {
if (<conditions>)
<custom fitness calculations...>;
else
return effect;
}
This would return a modified fitness value in certain conditions, but would return the standard fitness value otherwise.
More than one mutationEffect() callback may be defined to operate in the same tick. As with Eidos events, multiple callbacks will be called in the order in which they were defined in the input file. Furthermore, each callback will be given the effect value returned by the previous callback – so the value of effect is not necessarily the default value, in fact, but is the result of all previous mutationEffect() callbacks for that individual in that tick. In this way, the effects of multiple callbacks can “stack”.
One caveat to be aware of in WF models is that mutationEffect() callbacks are called at the end of the tick, just before the next tick begins. If you have a mutationEffect() callback defined for tick 10, for example, it will actually be called at the very end of tick 10, after child generation has finished, after the new children have been promoted to be the next parental generation, and after late() events have been executed. The fitness values calculated will thus be used during tick 11; the fitness values used in tick 10 were calculated at the end of tick 9. (This is primarily so that SLiMgui, which refreshes its display in between ticks, has computed fitness values at hand that it can use to display the new parental individuals in the proper colors.) This is not an issue in nonWF models, since fitness values are used in the same tick in which they are calculated.
If the randomizeCallbacks parameter to initializeSLiMOptions() is T (the default), the order in which the fitness of individuals is evaluated will be randomized within each subpopulation. This partially mitigates order-dependency issues, although such issues can still arise whenever the effects of a mutationEffect() callback are not independent. If randomizeCallbacks is F, the fitness of individuals will be evaluated in sequential order within each subpopulation, greatly increasing the risk of order-dependency problems.
Many other possibilities can be implemented with mutationEffect() callbacks. However, since mutationEffect() callbacks involve Eidos code being executed for the evaluation of fitness of every mutation of every individual (within the tick range, mutation type, and subpopulation specified), they can slow down a simulation considerably, so use them as sparingly as possible.
5.13.3 ITEM: 4. fitnessEffect() callbacks
We have already seen mutationEffect() callbacks, which modify the effect of a given mutation in a focal individual. Sometimes it is desirable to model effects upon individual fitness that are not governed by particular mutations (or not directly, at least); fitness effects due to spatial position, or resource acquisition, or behavior such as competitive or altruistic interactions, for example. Another situation of this type is when fitness depends upon the overall phenotype of an individual – the height of a tree, say – which might be influenced by genetics, but also by environmental effects, climate, and so forth. For these sorts of situations, SLiM provides fitnessEffect() callbacks.
A fitnessEffect() callback is called by SLiM when it is determining the fitness of an individual – typically, but not always, once per tick during the fitness calculation tick cycle stage. Normally, the fitness of a given individual is determined by multiplying together the fitness effects of all mutations possessed by that individual. Supplying a fitnessEffect() callback allows you to add another multiplicative fitness effect into that calculation. As with mutationEffect() callbacks, the value returned by fitnessEffect() callbacks is a fitness effect, so 1.0 is neutral.
The syntax for declaring fitnessEffect() callbacks is similar to that for mutationEffect() callbacks, but simpler since no mutation type is needed:
[id] [t1 [: t2]] fitnessEffect([<subpop-id>]) { ... }
(In multispecies models, the definition must be preceded by a species specification as usual.)
For example, if the callback were defined as:
1000:2000 fitnessEffect(p3) { 0.75; }
then a fitness effect of 0.75 would be multiplied into the fitness values of all individuals in subpopulation p3 from tick 1000 to tick 2000.
Much more interesting, of course, are fitnessEffect() callbacks that return different fitness effects for different individuals, depending upon their state! In addition to the standard SLiM globals, a fitnessEffect() callback is supplied with some additional information passed through “pseudo-parameters”, variables that are defined by SLiM within the context of the callback’s code to supply the callback with relevant information:
individual The focal individual (an object of class Individual)
subpop The subpopulation in which that individual lives
These may be used in the fitnessEffect() callback to compute a fitness effect that depends upon the state of the focal individual. The fitness effect for the callback is simply returned as a singleton float value, as usual.
More than one fitnessEffect() callback may be defined to operate in the same tick. Each such callback will provide an independent fitness effect for the focal individual; the results of each fitnessEffect() callback will be multiplied in to the individual’s fitness. These callbacks will generally be called once per individual in each tick, in an order that is formally undefined.
Beginning in SLiM 3.0, it is also possible to set the fitnessScaling property on a subpopulation to scale the fitness values of every individual in the subpopulation by the same constant amount, or to set the fitnessScaling property on an individual to scale the fitness value of that specific individual. These scaling factors are multiplied together with all other fitness effects for an individual to produce the individual’s final fitness value. The fitnessScaling properties of Subpopulation and Individual can often provide similar functionality to fitnessEffect() callbacks with greater efficiency and simplicity. They are reset to 1.0 in every tick for which a given species is active, immediately after fitness values are calculated, so they only need to be set when a value other than 1.0 is desired.
As with mutationEffect() callbacks, fitnessEffect() callbacks are called at the end of the tick, just before the next tick begins. Also, as with mutationEffect() callbacks, the order in which fitnessEffect() callbacks are called will be shuffled when randomizeCallbacks is enabled, as it is by default, partially mitigating order-dependency issues.
The fitnessEffect() callback mechanism is quite flexible and useful, although it has been considerably eclipsed by the modern modern and efficient fitnessScaling property mentioned above. When efficiency is not at a premium, it remains a clear and expressive paradigm for modeling individual-level fitness effects. The performance penalty paid is often not large, since these callbacks are called only once per individual per tick, whereas a mutationEffect() for a type of mutation that is common in the simulation might be called thousands of times per individual per tick (once per mutation of that type possessed by the focal individual). The performance penalty typically becomes severe only when the fitnessEffect() callback needs to perform calculations, once per focal individual, that would vectorize well if performed across a whole vector of individuals. In such cases, fitnessScaling should be used.
5.13.4 ITEM: 5. mateChoice() callbacks
Normally, WF models in SLiM regulate mate choice according to fitness; individuals of higher fitness are more likely to be chosen as mates. However, one might wish to simulate more complex mate-choice dynamics such as assortative or disassortative mating, mate search algorithms, and so forth. Such dynamics can be handled in WF models with the mateChoice() callback mechanism. (In nonWF models mating is arranged by the script, so there is no need for a callback.)
A mateChoice() callback is established in the input file with a syntax very similar to that of fitnessEffect() callbacks:
[id] [t1 [: t2]] mateChoice([<subpop-id>]) { ... }
(In multispecies models, the definition must be preceded by a species specification as usual.)
Note that if a subpopulation is given to which the mateChoice() callback is to apply, the callback is used for all matings that will generate a child in the stated subpopulation (as opposed to all matings of parents in the stated subpopulation); this distinction is important when migration causes children in one subpopulation to be generated by matings of parents in a different subpopulation.
When a mateChoice() callback is defined, the first parent in a mating is still chosen proportionally according to fitness (if you wish to influence that choice, you can use a mutationEffect() or fitnessEffect() callback). In a sexual (rather than hermaphroditic) simulation, this will be the female parent; SLiM does not currently support males as the choosy sex. The second parent – the male parent, in a sexual simulation – will then be chosen based upon the results of the mateChoice() callback.
More specifically, the callback must return a vector of weights, one for each individual in the subpopulation; SLiM will then choose a parent with probability proportional to weight. The mateChoice() callback could therefore modify or replace the standard fitness-based weights depending upon some other criterion such as assortativeness. A singleton object of type Individual may be returned instead of a weights vector to indicate that that specific individual has been chosen as the mate (beginning in SLiM 2.3); this could also be achieved by returned a vector of weights in which the chosen mate has a non-zero weight and all other weights are zero, but returning the chosen individual directly is much more efficient. A zero-length return vector – as generated by float(0), for example – indicates that a suitable mate was not found; in that event, a new first parent will be drawn from the subpopulation. Finally, if the callback returns NULL, that signifies that SLiM should use the standard fitness-based weights to choose a mate; the mateChoice() callback did not wish to alter the standard behavior for the current mating (this is equivalent to returning the unmodified vector of weights, but returning NULL is much faster since it allows SLiM to drop into an optimized case). Apart from the special cases described above – a singleton Individual, float(0), and NULL – the returned vector of weights must contain the same number of values as the size of the subpopulation, and all weights must be non-negative. Note that the vector of weights is not required to sum to 1, however; SLiM will convert relative weights on any scale to probabilities for you.
If the sum of the returned weights vector is zero, SLiM treats it as meaning the same thing as a return of float(0) – a suitable mate could not be found, and a new first parent will thus be drawn. (This is a change in policy beginning in SLiM 2.3; prior to that, returning a vector of sum zero was considered a runtime error.) There is a subtle difference in semantics between this and a return of float(0): returning float(0) immediately short-circuits mate choice for the current first parent, whereas returning a vector of zeros allows further applicable mateChoice() callbacks to be called, one of which might “rescue” the first parent by returning a non-zero weights vector or an individual. In most models this distinction is irrelevant, since chaining mateChoice() callbacks is uncommon. When the choice is otherwise unimportant, returning float(0) will be handled more quickly by SLiM.
In addition to the standard SLiM globals, a mateChoice() callback is supplied with some additional information passed through “pseudo-parameters”:
individual The parent already chosen (the female, in sexual simulations)
subpop The subpopulation into which the offspring will be placed
sourceSubpop The subpopulation from which the parents are being chosen
weights The standard fitness-based weights for all individuals
If sex is enabled, the mateChoice() callback must ensure that the appropriate weights are zero and nonzero to guarantee that all eligible mates are male (since the first parent chosen is always female, as explained above). In other words, weights for females must be 0. The weights vector given to the callback is guaranteed to satisfy this constraint. If sex is not enabled – in a hermaphroditic simulation, in other words – this constraint does not apply.
For example, a simple mateChoice() callback might look like this:
1000:2000 mateChoice(p2) {
return weights ^ 2;
}
This defines a mateChoice() callback for ticks 1000 to 2000 for subpopulation p2. The callback simply transforms the standard fitness-based probabilities by squaring them. Code like this could represent a situation in which fitness and mate choice proceed normally in one subpopulation (p1, here, presumably), but are altered by the effects of a social dominance hierarchy or male-male competition in another subpopulation (p2, here), such that the highest-fitness individuals tend to be chosen as mates more often than their (perhaps survival-based) fitness values would otherwise suggest. Note that by basing the returned weights on the weights vector supplied by SLiM, the requirement that females be given weights of 0 is finessed; in other situations, care would need to be taken to ensure that.
More than one mateChoice() callback may be defined to operate in the same tick. As with Eidos events, multiple callbacks will be called in the order in which they were defined. Furthermore, each callback will be given the weights vector returned by the previous callback – so the value of weights is not necessarily the default fitness-based weights, in fact, but is the result of all previous weights() callbacks for the current mate-choice event. In this way, the effects of multiple callbacks can “stack”. If any mateChoice() callback returns float(0), however – indicating that no eligible mates exist, as described above – then the remainder of the callback chain will be short-circuited and a new first parent will immediately be chosen.
Note that matings in SLiM do not proceed in random order. Offspring are generated for each subpopulation in turn, and within each subpopulation the order of offspring generation is also non-random with respect to both the source subpopulation and the sex of the offspring. It is important, therefore, that mateChoice() callbacks are not in any way biased by the offspring generation order; they should not treat matings early in the process any differently than matings late in the process. Any failure to guarantee such invariance could lead to large biases in the simulation outcome. In particular, it is usually dangerous to activate or deactivate mateChoice() callbacks while offspring generation is in progress.
A wide variety of mate choice algorithms can easily be implemented with mateChoice() callbacks. However, mateChoice() callbacks can be particularly slow since they are called for every proposed mating, and the vector of mating weights can be large and slow to process.
5.13.5 ITEM: 6. modifyChild() callbacks
Normally, a SLiM simulation defines child generation with its rules regarding selfing versus crossing, recombination, mutation, and so forth. However, one might wish to modify these rules in particular circumstances – by preventing particular children from being generated, by modifying the generated children in particular ways, or by generating children oneself. All of these dynamics can be handled in SLiM with the modifyChild() callback mechanism.
A modifyChild() callback is established in the input file with a syntax very similar to that of other callbacks:
[id] [t1 [: t2]] modifyChild([<subpop-id>]) { ... }
The modifyChild() callback may optionally be restricted to the children generated to occupy a specified subpopulation. (In multispecies models, the definition must be preceded by a species specification as usual.)
When a modifyChild() callback is called, a parent or parents have already been chosen, and a candidate child has already been generated. The parent or parents, and their genomes, are provided to the callback, as is the generated child and its genomes. The callback may accept the generated child, modify it, substitute completely different genomic information for it, or reject it (causing a new parent or parents to be selected and a new child to be generated, which will again be passed to the callback).
In addition to the standard SLiM globals, a modifyChild() callback is supplied with additional information passed through “pseudo-parameters”:
child The generated child (an object of class Individual)
parent1 The first parent (an object of class Individual)
isCloning T if the child is the result of cloning
isSelfing T if the child is the result of selfing (but see note below)
parent2 The second parent (an object of class Individual)
subpop The subpopulation in which the child will live
sourceSubpop The subpopulation of the parents (==subpop if not a migration mating)
These may be used in the modifyChild() callback to decide upon a course of action. The genomes of child (available as child.genome1 and child.genome2) may be modified by the callback; whatever mutations they contain on exit will be used for the new child. Alternatively, they may be left unmodified (to accept the generated child as is). These variables may be thought of as the two gametes that will fuse to produce the fertilized egg that results in a new offspring; child.genome1 is the gamete contributed by the first parent (the female, if sex is turned on), and child.genome2 is the gamete contributed by the second parent (the male, if sex is turned on). The child object itself may also be modified – for example, to set the spatial position of the child.
Importantly, a logical singleton return value is required from modifyChild() callbacks. Normally this should be T, indicating that generation of the child may proceed (with whatever modifications might have been made to the child’s genomes). A return value of F indicates that generation of this child should not continue; this will cause new parent(s) to be drawn, a new child to be generated, and a new call to the modifyChild() callback. A modifyChild() callback that always returns F can cause SLiM to hang, so be careful that it is guaranteed that your callback has a nonzero probability of returning T for every state your simulation can reach.
Note that isSelfing is T only when a mating was explicitly set up to be a selfing event by SLiM; an individual may also mate with itself by chance (by drawing itself as a mate) even when SLiM did not explicitly set up a selfing event, which one might term incidental selfing. If you need to know whether a mating event was an incidental selfing event, you can compare the parents; self-fertilization will always entail parent1==parent2, even when isSelfing is F. Since selfing is enabled only in non-sexual simulations, isSelfing will always be F in sexual simulations (and incidental selfing is also impossible in sexual simulations).
Note that matings in SLiM do not proceed in random order. Offspring are generated for each subpopulation in turn, and within each subpopulation the order of offspring generation is also non-random with respect to the source subpopulation, the sex of the offspring, and the reproductive mode (selfing, cloning, or autogamy). It is important, therefore, that modifyChild() callbacks are not in any way biased by the offspring generation order; they should not treat offspring generated early in the process any differently than offspring generated late in the process. Similar to mateChoice() callbacks, any failure to guarantee such invariance could lead to large biases in the simulation outcome. In particular, it is usually dangerous to activate or deactivate modifyChild() callbacks while offspring generation is in progress. When SLiM sees that mateChoice() or modifyChild() callbacks are defined, it randomizes the order of child generation within each subpopulation, so this issue is mitigated somewhat. However, offspring are still generated for each subpopulation in turn. Furthermore, in ticks without active callbacks offspring generation order will not be randomized (making the order of parents nonrandom in the next generation), with possible side effects. In short, order-dependency issues are possible and must be handled very carefully.
As with the other callback types, multiple modifyChild() callbacks may be registered and active. In this case, all registered and active callbacks will be called for each child generated, in the order that the callbacks were registered. If a modifyChild() callback returns F, however, indicating that the child should not be generated, the remaining callbacks in the chain will not be called.
There are many different ways in which a modifyChild() callback could be used in a simulation. In nonWF models, modifyChild() callbacks are often unnecessary since each generated child is available to the script in the models’ reproduction() callback anyway; but they may be used if desired.
5.13.6 ITEM: 7. recombination() callbacks
Typically, a simulation sets up a recombination map at the beginning of the run with initializeRecombinationRate(), and that map is used for the duration of the run. Less commonly, the recombination map is changed dynamically from tick to tick, with Chromosome’s method setRecombinationRate(); but still, a single recombination map applies for all individuals of a species in a given tick. However, in unusual circumstances a simulation may need to modify the way that recombination works on an individual basis; for this, the recombination() callback mechanism is provided. This can be useful for models involving chromosomal inversions that prevent recombination within a region for some individuals, for example, or for models of the evolution of recombination.
A recombination() callback is defined with a syntax much like that of other callbacks:
[id] [t1 [: t2]] recombination([<subpop-id>]) { ... }
The recombination() callback will be called during the generation of every gamete during the tick(s) in which it is active. It may optionally be restricted to apply only to gametes generated by parents in a specified subpopulation, using the <subpop-id> specifier. (In multispecies models, the definition must be preceded by a species specification as usual.)
When a recombination() callback is called, a parent has already been chosen to generate a gamete, and candidate recombination breakpoints for use in recombining the parental genomes have been drawn. The genomes of the focal parent are provided to the callback, as is the focal parent itself (as an Individual object) and the subpopulation in which it resides. Furthermore, the proposed breakpoints are provided to the callback. The callback may modify these breakpoints in order to change the breakpoints used, in which case it must return T to indicate that changes were made, or it may leave the proposed breakpoints unmodified, in which case it must return F. (The behavior of SLiM is undefined if the callback returns the wrong logical value.)
In addition to the standard SLiM globals, then, a recombination() callback is supplied with additional information passed through “pseudo-parameters”:
individual The focal parent that is generating a gamete
genome1 One genome of the focal parent; this is the initial copy strand
genome2 The other genome of the focal parent
subpop The subpopulation to which the focal parent belongs
breakpoints An integer vector of crossover breakpoints
These may be used in the recombination() callback to determine the final recombination breakpoints used by SLiM. If values are set into breakpoints, the new values must be of type integer. If breakpoints is modified by the callback, T should be returned, otherwise F should be returned (this is a speed optimization, so that SLiM does not have to spend time checking for changes when no changes have been made).
The positions specified in breakpoints mean that a crossover will occur immediately before the specified base position (between the preceding base and the specified base, in other words). The genome specified by genome1 will be used as the initial copy strand when SLiM executes the recombination; this cannot presently be changed by the callback. (Note that genome1 and genome2 will be the same objects as individual.genome1 and individual.genome2, but may be swapped, if individual.genome2 is the initial copy strand!)
In this design, the recombination callback does not specify a custom recombination map. Instead, the callback can add or remove breakpoints at specific locations. To implement a chromosomal inversion, for example, if the parent is heterozygous for the inversion mutation then crossovers within the inversion region are removed by the callback. As another example, to implement a model of the evolution of the overall recombination rate, a model could (1) set the global recombination rate to the highest rate attainable in the simulation, (2) for each individual, within the recombination() callback, calculate the fraction of that maximum rate that the focal individual would experience based upon its genetics, and (3) probabilistically remove proposed crossover points based upon random uniform draws compared to that threshold fraction, thus achieving the individual effective recombination rate desired. Other similar treatments could actually vary the effective recombination map, not just the overall rate, by removing proposed crossovers with probabilities that depend upon their position, allowing for the evolution of localized recombination hot-spots and cold-spots. Crossovers may also be added, not just removed, by recombination() callbacks.
In SLiM 3.3 the recombination model in SLiM was redesigned. This required a corresponding redesign of recombination() callbacks. In particular, the gcStarts and gcEnds pseudo-parameters to recombination() callbacks were removed. In the present design, the callback receives “crossover breakpoints” information only, in the breakpoints pseudo-parameter; it receives no information about gene conversion. However, recombination() callbacks can still be used with the “DSB” recombination model; at the point when the callback is called, the pattern of gene conversion tracts will have been simplified down to a vector of crossover breakpoints. “Complex” gene conversion tracts, however, involving heteroduplex mismatch repair, are not compatible with recombination() callbacks, since there is presently no way for them to be specified to the callback.
Note that the positions in breakpoints are not, in the general case, guaranteed to be sorted or uniqued; in other words, positions may appear out of order, and the same position may appear more than once. After all recombination() callbacks have completed, the positions from breakpoints will be sorted, uniqued, and used as the crossover points in generating the prospective gamete genome. The essential point here is that if the same position occurs more than once, across breakpoints, the multiple occurrences of the position do not cancel; SLiM does not cross over and then “cross back over” given a pair of identical positions. Instead, the multiple occurrences of the position will simply be uniqued down to a single occurrence.
As with the other callback types, multiple recombination() callbacks may be registered and active. In this case, all registered and active callbacks will be called for each gamete generated, in the order that the callbacks were registered.
5.13.7 ITEM: 8. interaction() callbacks
The InteractionType class provides various built-in interaction functions that translate from distances to interaction strengths. However, it may sometimes be useful to define a custom function for that purpose; for that reason, SLiM allows interaction() callbacks to be defined that modify the standard interaction strength calculated by InteractionType. In particular, this mechanism allows the strength of interactions to depend upon not only the distance between individuals, but also the genetics and other state of the individuals, the spatial position of the individuals, and other environmental variables.
An interaction() callback is called by SLiM when it is determining the strength of the interaction between one individual (the receiver of the interaction) and another individual (the exerter of the interaction). This generally occurs when an interaction query is made to InteractionType, as a side effect of serving that query. This means that interaction() callbacks may be called at a variety of points in the tick cycle, unlike the other callback types in SLiM, which are each called at a specific point. If you write an interaction() callback, you need to take this into account; assuming that the tick cycle is at a particular stage, or even that the tick or cycle is the same as it was when evaluate() was called, may be dangerous.
When an interaction strength is needed, the first thing SLiM does is calculate the default interaction strength using the interaction function that has been defined for the InteractionType. If the receiver is the same as the exerter, the interaction strength is always zero; and in spatial simulations if the distance between the receiver and the exerter is greater than the maximum distance set for the InteractionType, the interaction strength is also always zero. In these cases, interaction() callbacks will not be called, and there is no way to redefine these interaction strengths.
Otherwise, SLiM will then call interaction() callbacks that apply to the interaction type and exerter subpopulation for the interaction being evaluated. An interaction() callback is defined with a variation of the syntax used for other callbacks:
[id] [t1 [: t2]] interaction(<int-type-id> [, <subpop-id>]) { ... }
For example, if the callback were defined as:
1000:2000 interaction(i2, p3) { 1.0; }
then an interaction strength of 1.0 would be used for all interactions of interaction type i2, for exerters in subpopulation p3, from tick 1000 to tick 2000.
Beginning in SLiM 4, the receiver and exerter may be in different subpopulations from each other – or even, in multispecies models, in different species altogether. For the subpopulation id in the interaction() callback declaration, it does not matter which subpopulation the receiver is in; if the exerter is in p3, for the above example, then the interaction() callback will be called regardless of the receiver’s subpopulation (assuming other preconditions are also met, such as the tick range and the interaction type id). This means that interaction() callbacks are not species-specific, unlike other callback types; even if an interaction() callback is declared to be specific to exerters in p3, as above, receivers can still be in a different species. With no subpopulation id specified, interaction() callbacks are even more general: the InteractionType can then be evaluated and queried for receivers and exerters belonging to any species. For this reason, in multispecies models interaction() callbacks must be declared using a species specifier of species all, unlike all other SLiM callback types; it is not legal to declare an interaction() callback as species-specific. Note that there is no way to declare an interaction() callback as applying only to receivers in a given subpopulation; if that functionality is desired, you can test receiver.subpopulation in your callback code and act accordingly.
In addition to the standard SLiM globals, an interaction() callback is supplied with some additional information passed through “pseudo-parameters”:
distance The distance from receiver to exerter, in spatial simulations; NAN otherwise
strength The default interaction strength calculated by the interaction function
receiver The individual receiving the interaction (an object of class Individual)
exerter The individual exerting the interaction (an object of class Individual)
These may be used in the interaction() callback to compute an interaction strength. To simply use the default interaction strength that SLiM would use if a callback had not been defined for interaction type i1, for example, you could do this:
interaction(i1) {
return strength;
}
Usually an interaction() callback will modify that default strength based upon factors such as the genetics of the receiver and/or the exerter, the spatial positions of the two individuals, or some other simulation state. Any finite float value greater than or equal to 0.0 may be returned. The value returned will be not be cached by SLiM; if the interaction strength between the same two individuals is needed again later, the interaction() callback will be called again (something to keep in mind if the interaction strength includes a stochastic component). Note that the provided distance and strength values are based upon the spatial positions of the exerter and receiver when evaluate() was called, not their current spatial positions, if they have moved since the interaction was evaluated.
More than one interaction() callback may be defined to operate in the same tick. As with other callbacks, multiple callbacks will be called in the order in which they were defined in the input file. Furthermore, each callback will be given the strength value returned by the previous callback – so the value of strength is not necessarily the default value, in fact, but is the result of all previous interaction() callbacks for the interaction in question. In this way, the effects of multiple callbacks can “stack”.
The interaction() callback mechanism is extremely powerful and flexible, allowing any sort of user-defined interactions whatsoever to be queried dynamically using the methods of InteractionType. However, in the general case a simulation may call for the evaluation of the interaction strength between each individual and every other individual, making the computation of the full interaction network an O(N2) problem. Since interaction() callbacks may be called for each of those N2 interaction evaluations, they can slow down a simulation considerably, so it is recommended that they be used sparingly. This is the reason that the various interaction functions of InteractionType were provided; when an interaction does not depend upon individual state, the intention is to avoid the necessity of an interaction() callback altogether. Furthermore, constraining the number of cases in which interaction strengths need to be calculated – using a short maximum interaction distance, querying the nearest neighbors of the focal individual rather than querying all possible interactions with that individual, and specifying the reciprocality and sex segregation of the InteractionType, for example – may greatly decrease the computational overhead of interaction evaluation.
5.13.8 ITEM: 9. reproduction() callbacks
In WF models (the default model type in SLiM), the SLiM core manages the reproduction of individuals in each tick. In nonWF models, however, reproduction is managed by the model script, in reproduction() callbacks. These callbacks may only be defined in nonWF models.
A reproduction() callback is defined with a syntax much like that of other callbacks:
[id] [t1 [: t2]] reproduction([<subpop-id> [, <sex>]]) { ... }
The reproduction() callback will be called once for each individual during the tick(s) in which it is active. It may optionally be restricted to apply only to individuals in a specified subpopulation, using the <subpop-id> specifier; this may be a subpopulation specifier such as p1, or NULL indicating no restriction. It may also optionally be restricted to apply only to individuals of a specified sex (in sexual models), using the <sex> specifier; this may be "M" or "F", or NULL indicating no restriction. (In multispecies models, the definition must be preceded by a species specification as usual.)
When a reproduction() callback is called, SLiM’s expectation is that the callback will trigger the reproduction of a focal individual by making method calls to add new offspring individuals. Typically the offspring added are the offspring of the focal individual, and typically they are added to the subpopulation to which the focal individual belongs, but neither of these is required; a reproduction() callback may add offspring generated by any parent(s), to any subpopulation in the focal species. The focal individual is provided to the callback (as an Individual object), as are its genomes and the subpopulation in which it resides.
A common alternative pattern is for a reproduction() callback to ignore the focal individual and generate all of the offspring for a species for the current tick, from all parents. The callback then sets self.active to 0, preventing itself from being called again in the current tick; this callback design therefore executes once per tick. This can be useful if individuals influence each other’s offspring generation (as in a monogamous-mating model, for example); it can also simply be more efficient when producing offspring in bulk.
In addition to the usual SLiM globals, then, a reproduction() callback is supplied with additional information passed through global variables:
individual The focal individual that is expected to reproduce
subpop The subpopulation to which the focal individual belongs
At present, the return value from reproduction() callbacks is not used, and must be void (i.e., a value may not be returned). It is possible that other return values will be defined in future.
It is possible, of course, to do actions unrelated to reproduction inside reproduction() callbacks, but it is not recommended. The first() event phase of the current tick provides an opportunity for actions immediately before reproduction, and the early() event phase of the current tick provides an opportunity for actions immediately after reproduction, so only actions that are intertwined with reproduction itself should occur in reproduction() callbacks. Besides providing conceptual clarity, following this design principle will also decrease the probability of bugs, since actions that are unrelated to reproduction should usually not influence or be influenced by the dynamics of reproduction.
If the randomizeCallbacks parameter to initializeSLiMOptions() is T (the default), the order in which individuals are given an opportunity to reproduce with a call to reproduction() callbacks will be randomized within each subpopulation. This partially mitigates order-dependency issues, although such issues can still arise whenever the effects of a reproduction() callback are not independent. If randomizeCallbacks is F, individuals will be given their opportunity to reproduce in sequential order within each subpopulation, greatly increasing the risk of order-dependency problems.
As with the other callback types, multiple reproduction() callbacks may be registered and active. In this case, all registered and active callbacks will be called for each individual, in the order that the callbacks were registered.
5.13.9 ITEM: 10. mutation() callbacks
SLiM auto-generates new mutations according to the current mutation rate (or rate map) and the genetic structure defined by genomic elements, their genomic element types, the mutation types those genomic element types draw from, and the distribution of fitness effects defined by those mutation types. In nucleotide-based models, the nucleotide sequence and the mutation matrix also play a role in determining both the rate of mutation and the nucleotide mutated to. In some models it can be desirable to modify these dynamics in some way – altering the selection coefficients of new mutations in some way, changing the mutation type used, dictating the nucleotide to be used, replacing the proposed mutation with a pre-existing mutation at the same position, or even suppressing the proposed mutation altogether. To achieve this, one may define a mutation() callback.
A mutation() callback is defined as:
[id] [t1 [: t2]] mutation([<mut-type-id> [, <subpop-id>]]) { ... }
The mutation() callback will be called once for each new auto-generated mutation during the tick(s) in which the callback is active. It may optionally be restricted to apply only to mutations of a particular mutation type, using the <mut-type-id> specifier; this may be a mutation type specifier such as m1, or NULL indicating no restriction. It may also optionally be restricted to individuals generated by a specified subpopulation (usually – see below for discussion), using the <subpop-id> specifier; this should be a subpopulation specifier such as p1. (In multispecies models, the definition must be preceded by a species specification as usual.)
When a mutation() callback is called, a focal mutation (provided to the callback as an object of type Mutation) has just been created by SLiM, referencing a particular position in a parental genome (also provided, as an object of type Genome). The mutation will not be added to that parental genome; rather, the parental genome is being copied, during reproduction, to make a gamete or an offspring genome, and the mutation is, conceptually, a copying error made during that process. It will be added to the offspring genome that is the end result of the copying process (which may also involve recombination with another genome). At the point that the mutation() callback is called, the offspring genome is not yet created, however, and so it cannot be accessed from within the mutation() callback; the mutation() callback can affect only the mutation itself, not the genome to which the mutation will be added.
In addition to the standard SLiM globals, then, a mutation() callback is supplied with additional information passed through global variables:
mut The focal mutation that is being modified or reviewed
genome The parental genome that is being copied
element The genomic element that controls the mutation site
originalNuc The nucleotide (0/1/2/3 for A/C/G/T) originally at the mutating position
parent The parent which is generating the offspring genome
subpop The subpopulation to which the parent belongs
The mutation() callback has three possible returns: T, F, or (beginning in SLiM 3.5) a singleton object of type Mutation. A return of T indicates that the proposed mutation should be used in generating the offspring genome (perhaps with modifications made by the callback). Conversely, a return of F indicates that the proposed mutation should be suppressed. If a proposed mutation is suppressed, SLiM will not try again; one fewer mutations will be generated during reproduction than would otherwise have been true. Returning F will therefore mean that the realized mutation rate in the model will be lower than the expected mutation rate. Finally, a return of an object of type Mutation replaces the proposed mutation (mut) with the mutation returned; the offspring genomes being generated will contain the returned mutation. The position of the returned mutation must match that of the proposed mutation. This provides a mechanism for a mutation() callback to make SLiM re-use existing mutations instead of generating new mutations, which can be useful.
The callback may perform a variety of actions related to the generated mutation. The selection coefficient of the mutation can be changed with setSelectionCoefficient(), and the mutation type of the mutation can be changed with setMutationType(); the drawSelectionCoefficient() method of MutationType may also be useful here. A tag property value may be set for the mutation, and named values may be attached to the mutation with setValue(). In nucleotide-based models, the nucleotide (or nucleotideValue) property of the mutation may also be changed; note that the original nucleotide at the focal position in the parental genome is provided through originalNuc (it could be retrieved with genome.nucleotides(), but SLiM already has it at hand anyway). All of these modifications to the new mutation may be based upon the state of the parent, including its genetic state, or upon any other model state.
It is possible, of course, to do actions unrelated to mutation inside mutation() callbacks, but it is not recommended; first(), early(), and late() events should be used for general-purpose scripting. Besides providing conceptual clarity, following this design principle will also decrease the probability of bugs, since actions that are unrelated to mutation should not influence or be influenced by the dynamics of mutation.
The proposed mutation will not appear in the sim.mutations vector of segregating mutations until it has been added to a genome; it will therefore not be visible in that vector within its own mutation() callback invocation, and indeed, may not be visible in subsequent callbacks during the reproduction tick cycle stage until such time as the offspring individual being generated has been completed. If that offspring is ultimately rejected, in particular by a modifyChild() callback, the proposed mutation may not be used by SLiM at all. It may therefore be unwise to assume, in a mutation() callback, that the focal mutation will ultimately be added to the simulation, depending upon the rest of the model’s script.
There is one subtlety to be mentioned here, having to do with subpopulations. The subpop pseudo-parameter discussed above is always the subpopulation of the parent which possesses the genome that is being copied and is mutating; there is no ambiguity about that whatsoever. The <subpop-id> specified in the mutation() callback declaration, however, is a bit more subtle; above it was said that it restricts the callback “to individuals generated by a specified subpopulation”, and that is usually true but requires some explanation. In WF models, recall that migrants are generated in a source subpopulation and placed in a target subpopulation, as a model of juvenile migration; in that context, the <subpop-id> specifies the source subpopulation to which the mutation() callback will be restricted. In nonWF models, offspring are generated by the add...() family of Subpopulation methods, which can cross individuals from two different subpopulations and place the result in a third target subpopulation; in that context, in general, the <subpop-id> specifies the source subpopulation that is generating the particular gamete that is sustaining a mutation during its production. The exception to this rule is addRecombinant(); since there are four different source subpopulations potentially in play there, it was deemed simpler in that case for the <subpop-id> to specify the target subpopulation to which the mutation() callback will be restricted. If restriction to the source subpopulation is needed with addRecombinant(), the subpop pseudo-parameter may be consulted rather than using <subpop-id>.
Note that mutation() callbacks are only called for mutations that are auto-generated by SLiM, as a consequence of the mutation rate and the genetic structure defined. Mutations that are created in script, using addNewMutation() or addNewDrawnMutation(), will not trigger mutation() callbacks; but of course the script may modify or tailor such added mutations in whatever way is desired, so there is no need for callbacks in that situation.
As with the other callback types, multiple mutation() callbacks may be registered and active. In this case, all registered and active callbacks will be called for each generated mutation to which they apply, in the order that the callbacks were registered.
5.13.10 ITEM: 11. survival() callbacks
In nonWF models, a selection phase in the tick cycle results in mortality; individuals survive or die based upon their fitness. In most cases this standard behavior is sufficient; but occasionally it can be useful to observe the survival decisions SLiM makes (to log out information about dying individuals, for example), to modify those decisions (influencing which individuals live and which die, perhaps based upon factors other than genetics), or even to short-circuit mortality completely (moving dead individuals into a “cold storage” subpopulation for later use, perhaps). To accomplish such goals, one can the survival() callback mechanism to override SLiM’s default behavior. Note that in WF models, since they always model non-overlapping generations, the entire parental generation dies in each tick regardless of fitness; survival() callbacks therefore apply only to nonWF models.
A survival() callback is defined with a syntax much like that of other callbacks:
[id] [t1 [: t2]] survival([<subpop-id>]) { ... }
The survival() callback will be called during the selection phase of the tick cycle of nonWF models, during the tick(s) in which it is active. By default it will be called once per individual in the entire population (whether slated for survival or not); it may optionally be restricted to apply only to individuals in a specified subpopulation, using the <subpop-id> specifier. (In multispecies models, the definition must be preceded by a species specification as usual.)
When a survival() callback is called, a focal individual has already been evaluated by SLiM regarding its survival; a final fitness value for the individual has been calculated, and a random uniform draw in [0,1] has been generated that determines whether the individual is to survive (a draw less than the individual’s fitness) or die (a draw greater than or equal to the individual’s fitness). The focal individual is provided to the callback, as is the subpopulation in which it resides. Furthermore, the preliminary decision (whether the focal individual will survive or not), the focal individual’s fitness, and the random draw made by SLiM to determine survival are also provided to the callback. The callback may return NULL to accept SLiM’s decision, or may return T to indicate that the individual should survive, or F to indicate that it should die, regardless of its fitness and the random deviate drawn. The callback may also return a singleton Subpopulation object to indicate the individual should remain alive but should be moved to that subpopulation (note that calling takeMigrants() during the survival phase is illegal, because SLiM is busy modifying the population’s internal state).
In addition to the standard SLiM globals, then, a survival() callback is supplied with additional information passed through “pseudo-parameters”:
individual The focal individual that will live or die
subpop The subpopulation to which the focal individual belongs
surviving A logical value indicating SLiM’s preliminary decision (T == survival)
fitness The focal individual’s fitness
draw SLiM’s random uniform deviate, which determined the preliminary decision
These may be used in the survival() callback to determine the final decision.
While survival() callbacks are still being called, no decisions are put into effect; no individuals actually die, and none are moved to a new Subpopulation if that was requested. In effect, SLiM pre-plans the fate of every individual completely without modifying the model state at all. After all survival() callbacks have completed for every individual, the planned fates for every individual will then be executed, without any opportunity for further intervention through callbacks. It is therefore legal to inspect subpopulations and individuals inside a survival() callback, but it should be understood that previously made decisions about the fates of other individuals will not yet have any visible effect. It is generally a good idea for the decisions rendered by survival() callbacks to be independent anyway, to avoid biases due to order-dependency. If the randomizeCallbacks parameter to initializeSLiMOptions() is T (the default), the order in which survival() callbacks are called on individuals will be randomized within each subpopulation; nevertheless, order-dependency issues can occur if callback effects are not independent. If randomizeCallbacks is F, the order in which individuals are evaluated within each subpopulation is not guaranteed to be random, and order-dependency problems are thus even more likely.
It is worth noting that if survival() callbacks are used, “fitness” in the model is then no longer really fitness; the model is making its own decisions about which individuals live and die, and those decisions are the true determinant of fitness in the biological sense. A survival() callback that makes its own decisions regarding survival with no regard for SLiM’s calculated fitness values can completely alter the pattern of selection in a population, rendering all of SLiM’s fitness machinery – selection and dominance coefficients, fitnessScaling values, etc. – completely irrelevant. To avoid highly counterintuitive and confusing effects, it is thus generally a good idea to use of survival() callbacks only when it is strictly necessary to achieve a desired outcome.
As with the other callback types, multiple survival() callbacks may be registered and active. In this case, all registered and active callbacks will be called for each individual evaluated, in the order that the callbacks were registered.