Best way to track mutation segregation time?

34 views
Skip to first unread message

Alfredo Antonio Reis Marin

unread,
May 2, 2025, 11:01:00 AMMay 2
to slim-discuss

I’m studying trans-species polymorphism and need to measure how long each allele remains segregating in my SLiM simulation. My setup uses an infinite alleles model with a single genomic position, where mutations have stackPolicy = "l" and convertToSubstitution = F (since each allele affects fitness calculations).

The challenge is that once an allele is lost from the population, I lose references to it, making it impossible to query all alleles at the end of the simulation. Even if I stored references, the Mutation class doesn’t track when an allele disappears (i.e., its "end" tick). To calculate segregation times within SLiM, I’d need to actively monitor all alleles each generation, checking which ones were lost and recording their duration -- but this seems inefficient.

As an alternative, I’m considering logging all mutation IDs to a file during the simulation and processing them afterward to determine segregation times. However, I’m unsure if this is the most optimal approach. Is there a better or built-in way in SLiM to track how long alleles persist before being lost? Any advice on streamlining this process would be greatly appreciated!

Cheers,

AM

Ben Haller

unread,
May 3, 2025, 11:32:54 AMMay 3
to Alfredo Antonio Reis Marin, slim-discuss
Hi Alfredo!

This is a nice problem for illustrating that you *can* retain references to mutations that have been lost (or fixed), and why that's useful.  (See manual section 9.9 for a demonstration of that in a totally different context.  :->)  Here's an example model that solves your problem:

initialize() {
    initializeMutationType("m1", 0.5, "f", 0.0);
    initializeGenomicElementType("g1", m1, 1.0);
    
    initializeChromosome(1, 1e7);
    initializeGenomicElement(g1);
    initializeMutationRate(1e-7);
    initializeRecombinationRate(1e-8);
}
1 early() {
    sim.addSubpop("p1", 1000);
    defineGlobal("MUTS", sim.mutations);    // will be empty at this point
    defineGlobal("LIFETIME_TOTAL", 0);
    defineGlobal("LIFETIME_COUNT", 0);
}
late() {
    // find all mutations that are no longer segregating
    is_seg = MUTS.isSegregating;
    muts_nonseg = MUTS[!is_seg];
    
    // tabulate lifetimes for those that have been lost (not fixed)
    muts_lost = muts_nonseg[!muts_nonseg.isFixed];
    lost_lifetimes = community.tick - muts_lost.originTick;
    defineGlobal("LIFETIME_TOTAL", LIFETIME_TOTAL + sum(lost_lifetimes));
    defineGlobal("LIFETIME_COUNT", LIFETIME_COUNT + size(lost_lifetimes));
    
    // track all newly generated mutations
    // also, narrow down our tracking to those mutations still segregating
    new_muts = sim.mutations;
    new_muts = new_muts[new_muts.originTick == community.tick];
    defineGlobal("MUTS", c(MUTS[is_seg], new_muts));
    
    //catn(community.tick + " : tracking " + size(MUTS) + " mutations...");
}
2000 late() {
    catn("mean lifetime of mutations lost: " + (LIFETIME_TOTAL / LIFETIME_COUNT));
    catn("tracking " + size(MUTS) + " mutations at simulation end");
}

It's a SLiM 5 model, but the technique will work fine in SLiM 4 too; you'd just need to remove the initializeChromosome() call and set up the range of the genomic element explicitly.  :->

It works by keeping a list of mutations that are currently segregating in the global variable MUTS.  Every tick it updates that list (to avoid the memory usage exploding – even as it is, at the end of the model run it is watching ~30,000 mutations).  It can check the mutations in the list for having been lost, and remember information about how long they lived for in the global variables LIFETIME_TOTAL and LIFETIME_COUNT (sufficient to calculate a mean at the end).  It prints a summary at the end of the run, like:

mean lifetime of mutations lost: 10.1631
tracking 29204 mutations at simulation end

The overhead of this tracking is pretty small; in a test profile that I did in SLiMgui, the mutation tracking code was less than 20% of the total runtime.

Neat question, thanks!  Maybe it would be useful to have a simpler way to do this in SLiM, but I haven't thought of a way that I'm really happy with.  Keeping every lost mutation around (the way sim.substitutions keeps the fixed ones around) would potentially use a great deal of memory.  A callback that gets called when a mutation is lost or fixed would make it easy to solve the problem, but it seems like a lot of additional architectural complexity, benefiting only a tiny fraction of users.  So, for right now, the model above is the way you'd solve this problem.  :->

Happy modeling!

Cheers,
-B.

Benjamin C. Haller
Messer Lab
Cornell University


'Alfredo Antonio Reis Marin' via slim-discuss wrote on 5/2/25 4:01 PM:
--
SLiM forward genetic simulation: http://messerlab.org/slim/
---
You received this message because you are subscribed to the Google Groups "slim-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to slim-discuss...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/slim-discuss/903974a4-fbed-476d-a6bf-5fb87b823098n%40googlegroups.com.

Reply all
Reply to author
Forward
0 new messages