Skip to content

Commit

Permalink
Merge pull request #320 from grahamgower/docs
Browse files Browse the repository at this point in the history
Docs for __getitem__(), __contains__(), and migration_matrices()
  • Loading branch information
grahamgower authored Jun 8, 2021
2 parents 434622f + 0893d80 commit 7db3a05
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 16 deletions.
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,36 @@
# Changelog

## 0.1.2 - 2021-06-08

**New features**:

- Add `Graph.migration_matrices()` to get the migration matrices for a graph.
({user}`grahamgower`, {issue}`309`, {pr}`320`)
- Add `Deme.size_at()` to get the size of a deme at a given time.
({user}`grahamgower`, {issue}`312`, {pr}`314`)
- Support "linear" as an `Epoch.size_function`.
({user}`noscode`, {issue}`296`, {pr}`310`)
- Downstream test code can now use the `demes.hypothesis_strategies.graphs()`
[hypothesis](https://hypothesis.readthedocs.io/) strategy to generate a
random `Graph`. This is preliminary, and as such is not yet documented,
but is used for testing internally with some success. The API may change
in the future in response to requests from downstream application authors.
({user}`grahamgower`, {issue}`217`, {pr}`294`)
- The string representation for a graph, `Graph.__str__()`, is now the
simplified YAML output.
({user}`grahamgower`, {issue}`235`, {pr}`293`)

**Breaking changes**:

- The undocumented msprime and stdpopsim converters have been removed.
({user}`grahamgower`, {issue}`313`, {pr}`316`)
- The JSON spec doesn't allow serialising infinite float values (although the
Python json library does support this by default). So for JSON output we
instead use the string "Infinity".
({user}`grahamgower`,
[demes-spec#70](https://github.com/popsim-consortium/demes-spec/issues/70),
{pr}`311`)

## 0.1.1 - 2021-04-21

Remove the "demes" console_scripts entry point.
Expand Down
82 changes: 72 additions & 10 deletions demes/demes.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List, Union, Optional, Dict, MutableMapping, Any, Set
from typing import List, Union, Optional, Dict, MutableMapping, Any, Set, Tuple
import itertools
import math
import numbers
Expand Down Expand Up @@ -1239,15 +1239,35 @@ def __attrs_post_init__(self):
'if time_units!="generations", generation_time must be specified'
)

def __getitem__(self, deme_name):
def __getitem__(self, deme_name: Name) -> Deme:
"""
Return the :class:`.Deme` with the specified name.
Get the :class:`.Deme` with the specified name.
.. code::
graph = demes.load("gutenkunst_ooa.yml")
yri = graph["YRI"]
print(yri)
:param str deme_name: The name of the deme.
:rtype: Deme
:return: The deme.
"""
return self._deme_map[deme_name]

def __contains__(self, deme_name):
def __contains__(self, deme_name: Name) -> bool:
"""
Check if the graph contains a deme with the specified name.
.. code::
graph = demes.load("gutenkunst_ooa.yml")
if "CHB" in graph:
print("Deme CHB is in the graph")
:param str deme_name: The name of the deme.
:rtype: bool
:return: ``True`` if the deme is in the graph, ``False`` otherwise.
"""
return deme_name in self._deme_map

Expand Down Expand Up @@ -1611,10 +1631,52 @@ def _add_pulse(self, *, source, dest, proportion, time) -> Pulse:
self.pulses.append(new_pulse)
return new_pulse

def _migration_matrices(self):
def migration_matrices(self) -> Tuple[List[List[List[float]]], List[Number]]:
"""
Return a list of migration matrices, and a list of end times that
partition them. The start time for the first matrix is inf.
Get the migration matrices and the end times that partition them.
Returns a list of matrices, one for each time interval
over which migration rates do not change, in time-descending
order (from most ancient to most recent). For a migration matrix list
:math:`M`, the migration rate is :math:`M[i][j][k]` from deme
:math:`k` into deme :math:`j` during the :math:`i` 'th time interval.
The order of the demes' indices in each matrix matches the
order of demes in the graph's deme list (I.e. deme :math:`j`
corresponds to ``Graph.demes[j]``).
There is always at least one migration matrix in the list, even when
the graph defines no migrations.
A list of end times to which the matrices apply is also
returned. The time intervals to which the migration rates apply are an
open-closed interval ``(start_time, end_time]``, where the start time
of the first matrix is ``inf`` and the start time of subsequent
matrices match the end time of the previous matrix in the list.
.. note::
The last entry of the list of end times is always ``0``,
even when all demes in the graph go extinct before time ``0``.
.. code::
graph = demes.load("gutenkunst_ooa.yml")
mm_list, end_times = graph.migration_matrices()
start_times = [math.inf] + end_times[:-1]
assert len(mm_list) == len(end_times) == len(start_times)
deme_ids = {deme.name: j for j, deme in enumerate(graph.demes)}
j = deme_ids["YRI"]
k = deme_ids["CEU"]
for mm, start_time, end_time in zip(mm_list, start_times, end_times):
print(
f"CEU -> YRI migration rate is {mm[j][k]} during the "
f"time interval ({start_time}, {end_time}]"
)
:return: A 2-tuple of ``(mm_list, end_times)``,
where ``mm_list`` is a list of migration matrices,
and ``end_times`` are a list of end times for each matrix.
:rtype: tuple[list[list[list[float]]], list[float]]
"""
uniq_times = set(migration.start_time for migration in self.migrations)
uniq_times.update(migration.end_time for migration in self.migrations)
Expand All @@ -1624,7 +1686,7 @@ def _migration_matrices(self):
# Extend to t=0 even when there are no migrations.
end_times.append(0)
n = len(self.demes)
mm_list = [[[0] * n for _ in range(n)] for _ in range(len(end_times))]
mm_list = [[[0.0] * n for _ in range(n)] for _ in range(len(end_times))]
deme_id = {deme.name: j for j, deme in enumerate(self.demes)}
for migration in self.migrations:
start_time = math.inf
Expand All @@ -1640,7 +1702,7 @@ def _migration_matrices(self):
f"source={migration.source}, dest={migration.dest} "
f"between start_time={start_time}, end_time={end_time}"
)
mm_list[k][dest_id][source_id] = migration.rate
mm_list[k][dest_id][source_id] = float(migration.rate)
start_time = end_time
return mm_list, end_times

Expand All @@ -1650,7 +1712,7 @@ def _check_migration_rates(self):
deme in any interval of time.
"""
start_time = math.inf
mm_list, end_times = self._migration_matrices()
mm_list, end_times = self.migration_matrices()
for migration_matrix, end_time in zip(mm_list, end_times):
for j, row in enumerate(migration_matrix):
row_sum = sum(row)
Expand Down
4 changes: 4 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
.. autoclass:: demes.Graph
:members:
.. automethod:: __contains__
.. automethod:: __getitem__
.. autoclass:: demes.Deme
:members:
Expand Down
12 changes: 6 additions & 6 deletions tests/test_demes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2259,7 +2259,7 @@ def test_migration_matrices(self):
b = Builder()
b.add_deme("a", epochs=[dict(start_size=1)])
g = b.resolve()
mm_list, end_times = g._migration_matrices()
mm_list, end_times = g.migration_matrices()
assert end_times == [0]
assert mm_list == [[[0]]]

Expand All @@ -2268,7 +2268,7 @@ def test_migration_matrices(self):
b.add_deme("b", epochs=[dict(start_size=1)])
b.add_migration(source="a", dest="b", rate=0.1)
g = b.resolve()
mm_list, end_times = g._migration_matrices()
mm_list, end_times = g.migration_matrices()
assert end_times == [0]
assert mm_list == [[[0, 0], [0.1, 0]]]

Expand All @@ -2277,7 +2277,7 @@ def test_migration_matrices(self):
b.add_deme("b", epochs=[dict(start_size=1)])
b.add_migration(source="a", dest="b", rate=0.1, start_time=100, end_time=50)
g = b.resolve()
mm_list, end_times = g._migration_matrices()
mm_list, end_times = g.migration_matrices()
assert end_times == [100, 50, 0]
assert mm_list == [[[0, 0], [0, 0]], [[0, 0], [0.1, 0]], [[0, 0], [0, 0]]]

Expand All @@ -2286,7 +2286,7 @@ def test_migration_matrices(self):
b.add_deme("b", start_time=100, ancestors=["a"], epochs=[dict(start_size=1)])
b.add_migration(source="a", dest="b", rate=0.1)
g = b.resolve()
mm_list, end_times = g._migration_matrices()
mm_list, end_times = g.migration_matrices()
assert end_times == [100, 0]
assert mm_list == [[[0, 0], [0, 0]], [[0, 0], [0.1, 0]]]

Expand All @@ -2296,7 +2296,7 @@ def test_migration_matrices(self):
b.add_migration(source="a", dest="b", rate=0.1, end_time=100)
b.add_migration(source="a", dest="b", rate=0.1, start_time=50)
g = b.resolve()
mm_list, end_times = g._migration_matrices()
mm_list, end_times = g.migration_matrices()
assert end_times == [100, 50, 0]
assert mm_list == [[[0, 0], [0.1, 0]], [[0, 0], [0, 0]], [[0, 0], [0.1, 0]]]

Expand All @@ -2307,7 +2307,7 @@ def test_migration_matrices(self):
b.add_migration(source="a", dest="b", rate=0.1, end_time=50)
b.add_migration(source="c", dest="b", rate=0.2, start_time=100)
g = b.resolve()
mm_list, end_times = g._migration_matrices()
mm_list, end_times = g.migration_matrices()
assert end_times == [100, 50, 0]
assert mm_list == [
[[0, 0, 0], [0.1, 0, 0], [0, 0, 0]],
Expand Down

0 comments on commit 7db3a05

Please sign in to comment.