Skip to content

Commit 11dd2f5

Browse files
hyanwongmergify[bot]
authored andcommitted
Multi-arg tMRCA
Fixes #2801 #2070 #2611
1 parent 71f3e3f commit 11dd2f5

File tree

3 files changed

+35
-10
lines changed

3 files changed

+35
-10
lines changed

python/CHANGELOG.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
**Features**
66

7+
- Tree.trmca now accepts >2 nodes and returns nicer errors
8+
(:user:`hyanwong`, :pr:2808, :issue:`2801`, :issue:`2070`, :issue:`2611`)
9+
710
- Add ``TreeSequence.genetic_relatedness_weighted`` stats method.
811
(:user:`petrelharp`, :user:`brieuclehmann`, :user:`jeromekelleher`,
912
:pr:`2785`, :pr:`1246`)

python/tests/test_highlevel.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -886,42 +886,60 @@ def test_minlex_postorder_multiple_roots(self):
886886

887887

888888
class TestMRCA:
889+
"""
890+
Test both the tree.mrca and tree.tmrca methods.
891+
"""
892+
889893
t = tskit.Tree.generate_balanced(3)
890894
# 4
891895
# ┏━┻┓
892896
# ┃ 3
893897
# ┃ ┏┻┓
894898
# 0 1 2
895899

896-
def test_two_or_more_args(self):
897-
assert self.t.mrca(2, 1) == 3
898-
assert self.t.mrca(0, 1, 2) == 4
900+
@pytest.mark.parametrize("args, expected", [((2, 1), 3), ((0, 1, 2), 4)])
901+
def test_two_or_more_args(self, args, expected):
902+
assert self.t.mrca(*args) == expected
903+
assert self.t.tmrca(*args) == self.t.tree_sequence.nodes_time[expected]
899904

900905
def test_less_than_two_args(self):
901906
with pytest.raises(ValueError):
902907
self.t.mrca(1)
908+
with pytest.raises(ValueError):
909+
self.t.tmrca(1)
903910

904911
def test_no_args(self):
905912
with pytest.raises(ValueError):
906913
self.t.mrca()
914+
with pytest.raises(ValueError):
915+
self.t.tmrca()
907916

908917
def test_same_args(self):
909918
assert self.t.mrca(0, 0, 0, 0) == 0
919+
assert self.t.tmrca(0, 0, 0, 0) == self.t.tree_sequence.nodes_time[0]
910920

911921
def test_different_tree_levels(self):
912922
assert self.t.mrca(0, 3) == 4
923+
assert self.t.tmrca(0, 3) == self.t.tree_sequence.nodes_time[4]
913924

914925
def test_out_of_bounds_args(self):
915926
with pytest.raises(ValueError):
916927
self.t.mrca(0, 6)
928+
with pytest.raises(ValueError):
929+
self.t.tmrca(0, 6)
917930

918931
def test_virtual_root_arg(self):
919932
assert self.t.mrca(0, 5) == 5
933+
assert np.isposinf(self.t.tmrca(0, 5))
920934

921935
def test_multiple_roots(self):
922936
ts = tskit.Tree.generate_balanced(10).tree_sequence
923937
ts = ts.delete_intervals([ts.first().interval])
924938
assert ts.first().mrca(*ts.samples()) == tskit.NULL
939+
# We decided to raise an error for tmrca here, rather than report inf
940+
# see https://github.com/tskit-dev/tskit/issues/2801
941+
with pytest.raises(ValueError, match="do not share a common ancestor"):
942+
ts.first().tmrca(0, 6)
925943

926944

927945
class TestPathLength:

python/tskit/trees.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -996,7 +996,7 @@ def mrca(self, *args):
996996
"""
997997
Returns the most recent common ancestor of the specified nodes.
998998
999-
:param int `*args`: input node IDs, must be at least 2.
999+
:param int `*args`: input node IDs, at least 2 arguments are required.
10001000
:return: The node ID of the most recent common ancestor of the
10011001
input nodes, or :data:`tskit.NULL` if the nodes do not share
10021002
a common ancestor in the tree.
@@ -1015,12 +1015,12 @@ def get_tmrca(self, u, v):
10151015
# Deprecated alias for tmrca
10161016
return self.tmrca(u, v)
10171017

1018-
def tmrca(self, u, v):
1018+
def tmrca(self, *args):
10191019
"""
10201020
Returns the time of the most recent common ancestor of the specified
10211021
nodes. This is equivalent to::
10221022
1023-
>>> tree.time(tree.mrca(u, v))
1023+
>>> tree.time(tree.mrca(*args))
10241024
10251025
.. note::
10261026
If you are using this method to calculate average tmrca values along the
@@ -1031,12 +1031,16 @@ def tmrca(self, u, v):
10311031
nodes, for samples at time 0 the resulting statistics will be exactly
10321032
twice the tmrca value.
10331033
1034-
:param int u: The first node.
1035-
:param int v: The second node.
1036-
:return: The time of the most recent common ancestor of u and v.
1034+
:param `*args`: input node IDs, at least 2 arguments are required.
1035+
:return: The time of the most recent common ancestor of all the nodes.
10371036
:rtype: float
1037+
:raises ValueError: If the nodes do not share a single common ancestor in this
1038+
tree (i.e., if ``tree.mrca(*args) == tskit.NULL``)
10381039
"""
1039-
return self.get_time(self.get_mrca(u, v))
1040+
mrca = self.mrca(*args)
1041+
if mrca == tskit.NULL:
1042+
raise ValueError(f"Nodes {args} do not share a common ancestor in the tree")
1043+
return self.get_time(mrca)
10401044

10411045
def get_parent(self, u):
10421046
# Deprecated alias for parent

0 commit comments

Comments
 (0)