Skip to content

Commit

Permalink
Merge pull request #1936 from apragsdale/demes-sources
Browse files Browse the repository at this point in the history
Handle pulses in demes 0.2.0
  • Loading branch information
mergify[bot] authored Dec 1, 2021
2 parents 9b50ac7 + d771279 commit c3b86d3
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 33 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## [1.0.4] - 2021-12-01

**New features**:

- Support for Demes 0.2.0, which introduces a change to how pulse
sources and proportions are specified.
({pr}`1936`, {issue}`1930`, {user}`apragsdale`)

## [1.0.3] - 2021-11-12

This is a bugfix release recommended for all users.
Expand Down
18 changes: 10 additions & 8 deletions msprime/demography.py
Original file line number Diff line number Diff line change
Expand Up @@ -1982,12 +1982,14 @@ def get_growth_rate(epoch):
# Add pulses in reverse order, so that pulses with the same time
# correspond to the correct backwards-time mass migration ordering.
for pulse in reversed(events.pop("pulses")):
demography.add_mass_migration(
time=pulse.time,
source=pulse.dest,
dest=pulse.source,
proportion=pulse.proportion,
)
sequential_props = _proportions_to_sequential(pulse.proportions)
for prop, source in zip(sequential_props, pulse.sources):
demography.add_mass_migration(
time=pulse.time,
source=pulse.dest,
dest=source,
proportion=prop,
)
for merger in events.pop("mergers"):
demography.add_admixture(
time=merger.time,
Expand Down Expand Up @@ -2710,9 +2712,9 @@ def epoch_resolve(deme, time):
for lm in lineage_movements:
if (lm.source, lm.dest, lm.proportion) in pulses:
b.add_pulse(
source=resolved[lm.dest].name,
sources=[resolved[lm.dest].name],
dest=resolved[lm.source].name,
proportion=lm.proportion,
proportions=[lm.proportion],
time=time,
)

Expand Down
2 changes: 1 addition & 1 deletion requirements/CI-complete/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
bintrees==2.2.0
codecov==2.1.12
daiquiri==3.0.1
demes==0.1.2
demes==0.2.0
hypothesis==6.23.2
mypy==0.910
newick==1.3.1
Expand Down
4 changes: 2 additions & 2 deletions requirements/CI-docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
daiquiri==3.0.1
demes==0.1.2
demesdraw==0.1.4
demes==0.2.0
demesdraw==0.2.0
jupyter-book==0.12.1
matplotlib==3.5.0
newick==1.3.1
Expand Down
2 changes: 1 addition & 1 deletion requirements/CI-tests-conda/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ jsonschema<4.0
gsl
tskit==0.3.7
stdpopsim==0.1.2
demes==0.1.2
demes==0.2.0
2 changes: 1 addition & 1 deletion requirements/CI-tests-pip/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
bintrees==2.2.0
daiquiri==3.0.1
demes==0.1.2
demes==0.2.0
hypothesis==6.29.0
newick==1.3.0
numpy==1.21.4
Expand Down
2 changes: 1 addition & 1 deletion requirements/development.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ bintrees
codecov
coverage
daiquiri
demes>=0.1.2
demes>=0.2.0
demesdraw
flake8
hypothesis
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ install_requires =
numpy
newick>=1.3.0
tskit>=0.3.5
demes<0.2
demes>=0.2
setup_requires =
numpy
setuptools
Expand Down
97 changes: 79 additions & 18 deletions tests/test_demes.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,14 +286,14 @@ def test_pulses(self):
epochs:
- start_size: 2000
pulses:
- source: X
- sources: [X]
dest: A
time: 500
proportion: 0.1
- source: A
proportions: [0.1]
- sources: [A]
dest: X
time: 100
proportion: 0.2
proportions: [0.2]
"""
d = self.from_yaml(yaml)
assert d.num_populations == 2
Expand Down Expand Up @@ -340,10 +340,10 @@ def test_pulses_ordering(self):
- name: D
- name: E
pulses:
- {source: A, dest: B, time: 100, proportion: 0.1}
- {source: B, dest: C, time: 100, proportion: 0.2}
- {source: C, dest: D, time: 100, proportion: 0.3}
- {source: A, dest: E, time: 200, proportion: 0.4}
- {sources: [A], dest: B, time: 100, proportions: [0.1]}
- {sources: [B], dest: C, time: 100, proportions: [0.2]}
- {sources: [C], dest: D, time: 100, proportions: [0.3]}
- {sources: [A], dest: E, time: 200, proportions: [0.4]}
"""
d = self.from_yaml(yaml)
assert d.num_populations == 5
Expand All @@ -367,6 +367,24 @@ def test_pulses_ordering(self):
assert d.events[3].dest == "A"
assert d.events[3].proportion == 0.4

def test_multipulse(self):
b = demes.Builder(defaults=dict(epoch=dict(start_size=1)))
b.add_deme("a")
b.add_deme("b")
b.add_deme("c")
b.add_pulse(sources=["a", "b"], proportions=[0.1, 0.2], dest="c", time=1)
g = b.resolve()
d = msprime.Demography.from_demes(g)
assert len(d.events) == 2
assert isinstance(d.events[0], msprime.MassMigration)
assert isinstance(d.events[1], msprime.MassMigration)
assert d.events[0].source == "c"
assert d.events[0].dest == "a"
assert d.events[0].proportion == 0.1
assert d.events[1].source == "c"
assert d.events[1].dest == "b"
assert d.events[1].proportion == 0.2 / (1 - 0.1)

def test_merger(self):
yaml = """\
time_units: generations
Expand Down Expand Up @@ -865,9 +883,9 @@ def test_pulse(self):
assert len(graph.pulses) == 1
pulse = graph.pulses[0]
assert pulse.time == 100
assert pulse.source == "pop_1"
assert pulse.sources[0] == "pop_1"
assert pulse.dest == "pop_0"
assert pulse.proportion == 0.1
assert pulse.proportions[0] == 0.1

@pytest.mark.filterwarnings(
# demes warns about multiple pulses with the same time
Expand All @@ -884,21 +902,21 @@ def test_pulse_ordering(self):
assert len(graph.migrations) == 0
assert len(graph.pulses) == 4
pulses = graph.pulses
assert pulses[0].source == "pop_4"
assert pulses[0].sources[0] == "pop_4"
assert pulses[0].dest == "pop_3"
assert pulses[0].proportion == 0.4
assert pulses[0].proportions[0] == 0.4
assert pulses[0].time == 200
assert pulses[1].source == "pop_3"
assert pulses[1].sources[0] == "pop_3"
assert pulses[1].dest == "pop_2"
assert pulses[1].proportion == 0.3
assert pulses[1].proportions[0] == 0.3
assert pulses[1].time == 100
assert pulses[2].source == "pop_2"
assert pulses[2].sources[0] == "pop_2"
assert pulses[2].dest == "pop_1"
assert pulses[2].proportion == 0.2
assert pulses[2].proportions[0] == 0.2
assert pulses[2].time == 100
assert pulses[3].source == "pop_1"
assert pulses[3].sources[0] == "pop_1"
assert pulses[3].dest == "pop_0"
assert pulses[3].proportion == 0.1
assert pulses[3].proportions[0] == 0.1
assert pulses[3].time == 100

def test_bad_pulse(self):
Expand Down Expand Up @@ -1018,6 +1036,49 @@ def test_census_event_is_ignored(self):
demog.add_census(100)
demog.to_demes()

@pytest.mark.filterwarnings(
# demes warns about multiple pulses with the same time
"ignore:Multiple pulses:UserWarning"
)
def test_multipulse_roundtrip_two_sources(self):
b = demes.Builder(defaults=dict(epoch=dict(start_size=1)))
b.add_deme("a")
b.add_deme("b")
b.add_deme("c")
b.add_pulse(sources=["a", "b"], proportions=[0.1, 0.2], dest="c", time=1)
g = b.resolve()
d = msprime.Demography.from_demes(g)
g2 = d.to_demes()
assert len(g2.pulses) == 2
assert g2.pulses[0].sources[0] == "b"
assert g2.pulses[0].proportions[0] == 0.2 / (1 - 0.1)
assert g2.pulses[1].sources[0] == "a"
assert g2.pulses[1].proportions[0] == 0.1

@pytest.mark.filterwarnings(
# demes warns about multiple pulses with the same time
"ignore:Multiple pulses:UserWarning"
)
def test_multipulse_roundtrip_three_sources(self):
b = demes.Builder(defaults=dict(epoch=dict(start_size=1)))
b.add_deme("a")
b.add_deme("b")
b.add_deme("c")
b.add_deme("d")
b.add_pulse(
sources=["a", "b", "c"], proportions=[0.1, 0.2, 0.3], dest="d", time=1
)
g = b.resolve()
d = msprime.Demography.from_demes(g)
g2 = d.to_demes()
assert len(g2.pulses) == 3
assert g2.pulses[0].sources[0] == "c"
assert g2.pulses[0].proportions[0] == 0.3 / (1 - 0.1 - 0.2)
assert g2.pulses[1].sources[0] == "b"
assert g2.pulses[1].proportions[0] == 0.2 / (1 - 0.1)
assert g2.pulses[2].sources[0] == "a"
assert g2.pulses[2].proportions[0] == 0.1


# class TestRoundTrip:
# def remove_selfing_and_cloning(self, graph):
Expand Down

0 comments on commit c3b86d3

Please sign in to comment.