Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enable SLiM on Windows #1342

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion requirements/CI/conda.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
msprime==1.2.0
slim==4.0
slim==4.0.1
81 changes: 38 additions & 43 deletions stdpopsim/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@
pass


IS_WINDOWS = sys.platform.startswith("win")

logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -1040,47 +1038,44 @@ def time_or_model(
"This option may provided multiple times.",
)

# SLiM is not available for windows.
if not IS_WINDOWS:

def slim_exec(path):
# Hack to set the SLIM environment variable at parse time,
# before get_version() can be called.
os.environ["SLIM"] = path
return path

slim_parser = top_parser.add_argument_group("SLiM specific parameters")
slim_parser.add_argument(
"--slim-path",
metavar="PATH",
type=slim_exec,
default=None,
help="Full path to `slim' executable.",
)
slim_parser.add_argument(
"--slim-script",
action="store_true",
default=False,
help="Write script to stdout and exit without running SLiM.",
)
slim_parser.add_argument(
"--slim-scaling-factor",
metavar="Q",
default=1,
type=float,
help="Rescale model parameters by Q to speed up simulation. "
"See SLiM manual: `5.5 Rescaling population sizes to "
"improve simulation performance`. "
"[default=%(default)s].",
)
slim_parser.add_argument(
"--slim-burn-in",
metavar="X",
default=10,
type=float,
help="Length of the burn-in phase, in units of N generations "
"[default=%(default)s].",
)
def slim_exec(path):
# Hack to set the SLIM environment variable at parse time,
# before get_version() can be called.
os.environ["SLIM"] = path
return path

slim_parser = top_parser.add_argument_group("SLiM specific parameters")
slim_parser.add_argument(
"--slim-path",
metavar="PATH",
type=slim_exec,
default=None,
help="Full path to `slim' executable.",
)
slim_parser.add_argument(
"--slim-script",
action="store_true",
default=False,
help="Write script to stdout and exit without running SLiM.",
)
slim_parser.add_argument(
"--slim-scaling-factor",
metavar="Q",
default=1,
type=float,
help="Rescale model parameters by Q to speed up simulation. "
"See SLiM manual: `5.5 Rescaling population sizes to "
"improve simulation performance`. "
"[default=%(default)s].",
)
slim_parser.add_argument(
"--slim-burn-in",
metavar="X",
default=10,
type=float,
help="Length of the burn-in phase, in units of N generations "
"[default=%(default)s].",
)

subparsers = top_parser.add_subparsers(dest="subcommand")
subparsers.required = True
Expand Down
43 changes: 26 additions & 17 deletions stdpopsim/slim_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@

import os
import sys
import contextlib
import copy
import string
import tempfile
import subprocess
import functools
import itertools
import contextlib
import random
import textwrap
import logging
Expand All @@ -62,6 +62,12 @@

logger = logging.getLogger(__name__)


def _escape_eidos(s):
# this is for Windows paths passed as strings in Eidos
return "\\\\".join(s.split("\\"))


_slim_upper = """
initialize() {
if (!exists("dry_run"))
Expand Down Expand Up @@ -1135,7 +1141,7 @@ def fix_time(event):
recombination_rates=recomb_rates_str,
recombination_ends=recomb_ends_str,
generation_time=demographic_model.generation_time,
trees_file=trees_file,
trees_file=_escape_eidos(trees_file),
pop_names=f"c({pop_names_str})",
)
)
Expand Down Expand Up @@ -1410,7 +1416,7 @@ def matrix2str(
if logfile is not None:
printsc(
string.Template(_slim_logfile).substitute(
logfile=logfile,
logfile=_escape_eidos(str(logfile)),
loginterval=logfile_interval,
)
)
Expand Down Expand Up @@ -1552,21 +1558,27 @@ def simulate(

run_slim = not slim_script

mktemp = functools.partial(tempfile.NamedTemporaryFile, mode="w")
tempdir = tempfile.TemporaryDirectory(prefix="stdpopsim_")
ts_filename = os.path.join(tempdir.name, f"{os.urandom(3).hex()}.trees")

@contextlib.contextmanager
def script_file_f():
f = mktemp(suffix=".slim") if not slim_script else sys.stdout
yield f
if run_slim:
fname = os.path.join(tempdir.name, f"{os.urandom(3).hex()}.slim")
f = open(fname, "w")
else:
fname = "stdout"
f = sys.stdout
yield f, fname
# Don't close sys.stdout.
if not slim_script:
if run_slim:
f.close()

with script_file_f() as script_file, mktemp(suffix=".ts") as ts_file:

with script_file_f() as sf:
script_file, script_filename = sf
recap_epoch = slim_makescript(
script_file,
ts_file.name,
ts_filename,
demographic_model,
contig,
sample_sets,
Expand All @@ -1584,7 +1596,7 @@ def script_file_f():
return None

self._run_slim(
script_file.name,
script_filename,
slim_path=slim_path,
seed=seed,
dry_run=dry_run,
Expand All @@ -1594,7 +1606,7 @@ def script_file_f():
if dry_run:
return None

ts = tskit.load(ts_file.name)
ts = tskit.load(ts_filename)

ts = _add_dfes_to_metadata(ts, contig)
if _recap_and_rescale:
Expand Down Expand Up @@ -1634,8 +1646,7 @@ def _run_slim(
if slim_path is None:
slim_path = self.slim_path()

# SLiM v3.6 sends `stop()` output to stderr, which we rely upon.
self._assert_min_version("3.6", slim_path)
self._assert_min_version("4.0", slim_path)

slim_cmd = [slim_path]
if seed is not None:
Expand Down Expand Up @@ -1921,6 +1932,4 @@ def recap_and_rescale(
return ts


# SLiM does not currently work on Windows.
if sys.platform != "win32":
stdpopsim.register_engine(_SLiMEngine())
stdpopsim.register_engine(_SLiMEngine())
3 changes: 0 additions & 3 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,9 +543,6 @@ def test_gutenkunst_three_pop_ooa(self):
def test_browning_america(self):
self.verify_bad_samples("HomSap -d AmericanAdmixture_4B11 2 3 4 5 6")

IS_WINDOWS = sys.platform.startswith("win")

@pytest.mark.skipif(IS_WINDOWS, reason="SLiM not available on windows")
def test_browning_america_dfe(self):
self.verify_bad_samples(
"HomSap -d AmericanAdmixture_4B11 --dfe Gamma_K17 2 3 4 5 6"
Expand Down
6 changes: 0 additions & 6 deletions tests/test_dfes.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
from stdpopsim import dfe
from stdpopsim import utils

IS_WINDOWS = sys.platform.startswith("win")


class TestCreateMutationType:
"""
Expand Down Expand Up @@ -554,7 +552,6 @@ def test_dfe_is_neutral(self):
)
assert d.is_neutral is (neutral and dist == "f")

@pytest.mark.skipif(IS_WINDOWS, reason="SLiM not available on windows")
def test_no_msprime_dfe(self):
# test we cannot simulate a non-neutral DFE with msprime
m1 = dfe.MutationType(
Expand Down Expand Up @@ -644,7 +641,6 @@ def test_bad_qc_dfe(self):
)


@pytest.mark.skipif(IS_WINDOWS, reason="SLiM not available on windows")
class DFETestMixin:
"""
Mixin for testing specific DFEs. Subclass should extend
Expand Down Expand Up @@ -694,7 +690,6 @@ def test_simulation_runs(self):
assert num_nonneutral > 0 # nonneutral mutations


@pytest.mark.skipif(IS_WINDOWS, reason="SLiM not available on windows")
class CatalogDFETestMixin(DFETestMixin):
"""
Mixin for DFEs in the catalog.
Expand All @@ -704,7 +699,6 @@ def test_id_valid(self):
assert utils.is_valid_dfe_id(self.dfe.id)


@pytest.mark.skipif(IS_WINDOWS, reason="SLiM not available on windows")
class QcdCatalogDFETestMixin(CatalogDFETestMixin):
"""
Extends the tests to also check that the qc DFE is equal to
Expand Down
4 changes: 0 additions & 4 deletions tests/test_masking.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
"""
Tests for the genetic maps management.
"""
import sys

import numpy as np
import msprime
import pytest

import stdpopsim.utils

IS_WINDOWS = sys.platform.startswith("win")


class TestMasking:
@pytest.mark.usefixtures("tmp_path")
Expand Down Expand Up @@ -119,7 +116,6 @@ def test_mask_tree_sequence(self):
assert np.all(np.logical_and(100 <= positions, positions < 200))


@pytest.mark.skipif(IS_WINDOWS, reason="SLiM not available on windows")
class TestSimulate:
@pytest.mark.filterwarnings("ignore::msprime.IncompletePopulationMetadataWarning")
def test_simulate_with_mask(self):
Expand Down
Loading