Skip to content

Commit 325c2ec

Browse files
committed
Add recapitation, and make it the default.
Closes popsim-consortium#259.
1 parent 5c8ca58 commit 325c2ec

File tree

2 files changed

+75
-51
lines changed

2 files changed

+75
-51
lines changed

stdpopsim/slim_engine.py

Lines changed: 64 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import itertools
4848
import collections
4949
import contextlib
50+
import random
5051

5152
import stdpopsim
5253
import numpy as np
@@ -122,7 +123,7 @@
122123
}
123124
}
124125
125-
// Check if burn-in has completed.
126+
// Check if burn in has completed.
126127
1 {
127128
if (!check_coalescence) {
128129
setup();
@@ -152,7 +153,7 @@
152153
sim.registerEarlyEvent(NULL, "{setup();}", g, g);
153154
} else {
154155
if (sim.generation == 1)
155-
dbg("Waiting for burn-in...");
156+
dbg("Waiting for burn in...");
156157
// Reschedule the current script block 10 generations hence.
157158
// XXX: find a less arbitrary generation interval.
158159
g = sim.generation + 10;
@@ -165,7 +166,7 @@
165166
166167
dbg("setup()");
167168
168-
// Once burn-in is complete, we know the starting generation (which
169+
// Once burn in is complete, we know the starting generation (which
169170
// corresponds to T_0) and can thus calculate the generation
170171
// for each remaining event.
171172
G_start = sim.generation;
@@ -554,18 +555,19 @@ def get_version(self):
554555
return s.split()[2].decode("ascii").rstrip(",")
555556

556557
def simulate(
557-
self, demographic_model=None, contig=None, samples=None,
558-
seed=None, verbosity=0,
559-
slim_script=False, slim_scaling_factor=10, slim_no_burnin=False,
560-
slim_path=None,
561-
**kwargs):
558+
self, demographic_model=None, contig=None, samples=None, seed=None,
559+
verbosity=0, slim_path=None, slim_script=False, slim_scaling_factor=10,
560+
slim_no_recapitation=False, slim_no_burnin=False, **kwargs):
562561
"""
563562
Simulate the demographic model using SLiM.
564563
See :meth:`.Engine.simulate()` for definitions of the
565564
``demographic_model``, ``contig``, and ``samples`` parameters.
566565
567566
:param seed: The seed for the random number generator.
568567
:type seed: int
568+
:param slim_path: The full path to the slim executable, or the name of
569+
a command in the current PATH.
570+
:type slim_path: str
569571
:param slim_script: If true, the simulation will not be executed.
570572
Instead the generated SLiM script will be printed to stdout.
571573
:type slim_script: bool
@@ -576,23 +578,34 @@ def simulate(
576578
See SLiM manual: `5.5 Rescaling population sizes to improve
577579
simulation performance.`
578580
:type slim_scaling_factor: float
579-
:param slim_no_burnin: Do not perform a `burn in` at the start of the
580-
simulation. The default `burn in` behaviour is to wait until all
581-
individuals in the ancestral population(s) have a common ancestor
582-
within their respective population, and then wait another 10*N
583-
generations.
581+
:param slim_no_recapitation: Do an explicit burn in, and add
582+
mutations, within the SLiM simulation. This may be much slower than
583+
the defaults (recapitation and neutral mutation overlay with
584+
msprime). The burn in behaviour is to wait until all individuals in
585+
the ancestral populations have a common ancestor within their
586+
respective population, and then wait another 10*N generations.
587+
:type slim_no_recapitation: bool
588+
:param slim_no_burnin: Do not perform a burn in at the start of the
589+
simulation. This option is only relevant when
590+
``slim_no_recapitation=True``.
584591
:type slim_no_burnin: bool
585-
:param slim_path: The full path to the slim executable, or the name of
586-
a command in the current PATH.
587-
:type slim_path: str
588592
"""
589593

590594
run_slim = not slim_script
591-
check_coalescence = not slim_no_burnin
595+
do_recap = not slim_no_recapitation
596+
check_coalescence = slim_no_recapitation and not slim_no_burnin
592597

593598
if slim_path is None:
594599
slim_path = self.slim_path()
595600

601+
if do_recap:
602+
mutation_rate = contig.mutation_rate
603+
# Ensure no mutations are introduced by SLiM.
604+
contig = stdpopsim.Contig(
605+
recombination_map=contig.recombination_map,
606+
mutation_rate=0,
607+
genetic_map=contig.genetic_map)
608+
596609
slim_cmd = [slim_path]
597610
if seed is not None:
598611
slim_cmd.extend(["-s", f"{seed}"])
@@ -625,41 +638,28 @@ def script_file_f():
625638

626639
ts = pyslim.load(ts_file.name)
627640

628-
# random.seed(seed)
629-
# s1, s2 = random.randint(1,2**32-1), random.randint(1,2**32-1)
641+
if do_recap:
642+
rng = random.Random(seed)
643+
s1, s2 = rng.randrange(1, 2**32), rng.randrange(1, 2**32)
630644

631-
# Recapitation.
632-
# r = recombination_map.mean_recombination_rate
633-
# N0 = ?
634-
# ts = ts.recapitate(Ne=N0, recombination_rate=r, random_seed=s1)
645+
# Recapitation.
646+
r_map = contig.recombination_map
647+
pop_configs = demographic_model.population_configurations
648+
ts = ts.recapitate(
649+
recombination_rate=r_map.mean_recombination_rate,
650+
population_configurations=pop_configs,
651+
random_seed=s1)
635652

636653
ts = simplify_remembered(ts)
637654

638-
# Add neutral mutations.
639-
# ts = pyslim.SlimTreeSequence(msprime.mutate(ts, rate=mutation_rate,
640-
# keep=True, random_seed=s2))
655+
if do_recap:
656+
# Add neutral mutations.
657+
ts = pyslim.SlimTreeSequence(msprime.mutate(
658+
ts, rate=mutation_rate, keep=True, random_seed=s2))
641659

642660
return ts
643661

644662
def add_arguments(self, parser):
645-
parser.add_argument(
646-
"--slim-scaling-factor", metavar="Q", default=10, type=float,
647-
help="Rescale model parameters by Q to speed up simulation "
648-
"See SLiM manual: `5.5 Rescaling population sizes to "
649-
"improve simulation performance`. "
650-
"[%(default)s].")
651-
parser.add_argument(
652-
"--slim-script", action="store_true", default=False,
653-
help="Write script to stdout and exit without running SLiM.")
654-
parser.add_argument(
655-
"--slim-no-burnin", action="store_true", default=False,
656-
help="Don't wait for coalescence in SLiM before proceeding.")
657-
# parser.add_argument(
658-
# "--pyslim-recap", action="store_true", default=False,
659-
# help="Recapitate trees with pyslim, and overlay neutral "
660-
# "mutations with msprime, after running SLiM."
661-
# "This implies --slim-no-burnin.")
662-
663663
def slim_exec(path):
664664
# Hack to set the SLIM environment variable at parse time,
665665
# before get_version() can be called.
@@ -668,6 +668,26 @@ def slim_exec(path):
668668
parser.add_argument(
669669
"--slim-path", metavar="PATH", type=slim_exec, default=None,
670670
help="Full path to `slim' executable.")
671+
parser.add_argument(
672+
"--slim-script", action="store_true", default=False,
673+
help="Write script to stdout and exit without running SLiM.")
674+
parser.add_argument(
675+
"--slim-scaling-factor", metavar="Q", default=10, type=float,
676+
help="Rescale model parameters by Q to speed up simulation. "
677+
"See SLiM manual: `5.5 Rescaling population sizes to "
678+
"improve simulation performance`. "
679+
"[default=%(default)s].")
680+
parser.add_argument(
681+
"--slim-no-recapitation", action="store_true", default=False,
682+
help="Explicitly wait for coalescence, and add "
683+
"mutations, within the SLiM simulation. This may be much "
684+
"slower than the defaults (recapitation and neutral mutation "
685+
"overlay with msprime).")
686+
parser.add_argument(
687+
"--slim-no-burnin", action="store_true", default=False,
688+
help="Don't wait for coalescence in SLiM before proceeding. "
689+
"This option is only relevant in combination with "
690+
"--slim-no-recapitation.")
671691

672692

673693
stdpopsim.register_engine(_SLiMEngine())

tests/test_slim_engine.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,9 @@ def test_script_generation(self):
9393

9494
@unittest.skipIf(sys.platform.startswith("win"), "no conda slim package for windows")
9595
def test_simulate(self):
96-
with tempfile.NamedTemporaryFile(mode="w") as f:
97-
self.docmd(f"HomSap -o {f.name}")
98-
ts = tskit.load(f.name)
99-
self.assertEqual(ts.num_samples, 10)
100-
10196
saved_slim_env = os.environ.get("SLIM")
102-
10397
with tempfile.NamedTemporaryFile(mode="w") as f:
104-
self.docmd(f"--slim-no-burnin --slim-path slim HomSap -o {f.name}")
98+
self.docmd(f"--slim-path slim HomSap -o {f.name}")
10599
ts = tskit.load(f.name)
106100
self.assertEqual(ts.num_samples, 10)
107101

@@ -110,6 +104,16 @@ def test_simulate(self):
110104
else:
111105
os.environ["SLIM"] = saved_slim_env
112106

107+
with tempfile.NamedTemporaryFile(mode="w") as f:
108+
self.docmd(f"--slim-no-recapitation HomSap -o {f.name}")
109+
ts = tskit.load(f.name)
110+
self.assertEqual(ts.num_samples, 10)
111+
112+
with tempfile.NamedTemporaryFile(mode="w") as f:
113+
self.docmd(f"--slim-no-recapitation --slim-no-burnin HomSap -o {f.name}")
114+
ts = tskit.load(f.name)
115+
self.assertEqual(ts.num_samples, 10)
116+
113117
def test_bad_slim_environ_var(self):
114118
saved_slim_env = os.environ.get("SLIM")
115119

0 commit comments

Comments
 (0)