Skip to content

Commit 1832aee

Browse files
authored
Add tests for bad mutation parents (#3321)
1 parent 23442b5 commit 1832aee

1 file changed

Lines changed: 138 additions & 0 deletions

File tree

python/tests/test_tables.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3568,6 +3568,57 @@ def test_compute_mutation_parents_restores_on_index_error(self):
35683568
tables.compute_mutation_parents()
35693569
assert tables.mutations.parent[0] == 123
35703570

3571+
def test_compute_mutation_parents_tolerates_various_invalid_values(self):
3572+
tables = tskit.TableCollection(sequence_length=1.0)
3573+
parent = tables.nodes.add_row(time=1.0)
3574+
child = tables.nodes.add_row(time=0, flags=tskit.NODE_IS_SAMPLE)
3575+
tables.edges.add_row(left=0.0, right=1.0, parent=parent, child=child)
3576+
site = tables.sites.add_row(position=0.0, ancestral_state="A")
3577+
tables.mutations.add_row(site=site, node=child, derived_state="C")
3578+
tables.build_index()
3579+
3580+
# A range of nonsensical parent values should be ignored
3581+
invalid_values = [
3582+
-2, # less than NULL sentinel
3583+
0, # equal to self for single-row case
3584+
1, # out of bounds (>= num_rows)
3585+
42, # arbitrary out of bounds
3586+
np.iinfo(np.int32).max,
3587+
]
3588+
for val in invalid_values:
3589+
tables.mutations.parent[:] = val
3590+
tables.compute_mutation_parents()
3591+
assert tables.mutations.parent[0] == tskit.NULL
3592+
3593+
def test_compute_mutation_parents_tolerates_cross_site_and_loops(self):
3594+
# Build a simple tree with 2 samples under a common parent
3595+
tables = tskit.TableCollection(sequence_length=1.0)
3596+
root = tables.nodes.add_row(time=2.0)
3597+
a = tables.nodes.add_row(time=0, flags=tskit.NODE_IS_SAMPLE)
3598+
b = tables.nodes.add_row(time=0, flags=tskit.NODE_IS_SAMPLE)
3599+
tables.edges.add_row(0.0, 1.0, root, a)
3600+
tables.edges.add_row(0.0, 1.0, root, b)
3601+
s0 = tables.sites.add_row(0.0, "A")
3602+
s1 = tables.sites.add_row(0.5, "A")
3603+
m0 = tables.mutations.add_row(site=s0, node=a, derived_state="C")
3604+
m1 = tables.mutations.add_row(site=s1, node=b, derived_state="G")
3605+
assert m0 == 0 and m1 == 1
3606+
tables.build_index()
3607+
3608+
# Cross-site parent should be ignored by compute_mutation_parents
3609+
tables.mutations.parent[:] = np.array([tskit.NULL, 0], dtype=np.int32)
3610+
tables.compute_mutation_parents()
3611+
assert np.array_equal(
3612+
tables.mutations.parent, np.array([tskit.NULL, tskit.NULL])
3613+
)
3614+
3615+
# Explicit loop in parents should be ignored by compute_mutation_parents
3616+
tables.mutations.parent[:] = np.array([1, 0], dtype=np.int32)
3617+
tables.compute_mutation_parents()
3618+
assert np.array_equal(
3619+
tables.mutations.parent, np.array([tskit.NULL, tskit.NULL])
3620+
)
3621+
35713622
def test_str(self):
35723623
ts = msprime.simulate(10, random_seed=1)
35733624
tables = ts.tables
@@ -5768,3 +5819,90 @@ def test_ragged_selection_indices_non_monotonic():
57685819
gather = _ragged_selection_indices(indexed_offsets, lengths64)
57695820
expected = np.array([5, 0, 1], dtype=np.int64)
57705821
assert np.array_equal(gather, expected)
5822+
5823+
5824+
class TestMutationParentValidation:
5825+
def _two_leaf_tree(self):
5826+
tables = tskit.TableCollection(sequence_length=1.0)
5827+
root = tables.nodes.add_row(time=2.0)
5828+
a = tables.nodes.add_row(time=0, flags=tskit.NODE_IS_SAMPLE)
5829+
b = tables.nodes.add_row(time=0, flags=tskit.NODE_IS_SAMPLE)
5830+
tables.edges.add_row(0.0, 1.0, root, a)
5831+
tables.edges.add_row(0.0, 1.0, root, b)
5832+
return tables, a, b
5833+
5834+
def _chain_tree(self):
5835+
tables = tskit.TableCollection(sequence_length=1.0)
5836+
root = tables.nodes.add_row(time=2.0)
5837+
mid = tables.nodes.add_row(time=1.0)
5838+
leaf = tables.nodes.add_row(time=0, flags=tskit.NODE_IS_SAMPLE)
5839+
tables.edges.add_row(0.0, 1.0, root, mid)
5840+
tables.edges.add_row(0.0, 1.0, mid, leaf)
5841+
return tables, mid, leaf
5842+
5843+
def test_tree_sequence_bad_mutation_parent_topology(self):
5844+
tables, a, b = self._two_leaf_tree()
5845+
s = tables.sites.add_row(0.0, "A")
5846+
tables.mutations.add_row(site=s, node=a, derived_state="C") # id 0
5847+
tables.mutations.add_row(site=s, node=b, derived_state="G") # id 1
5848+
# Make a mutation on a parallel branch the parent
5849+
mut_cols = tables.mutations.asdict()
5850+
mut_cols["parent"] = np.array([tskit.NULL, 0], dtype=np.int32)
5851+
tables.mutations.set_columns(**mut_cols)
5852+
with pytest.raises(tskit.LibraryError, match="TSK_ERR_BAD_MUTATION_PARENT"):
5853+
tables.tree_sequence()
5854+
5855+
def test_tree_sequence_mutation_parent_after_child(self):
5856+
tables, mid, leaf = self._chain_tree()
5857+
s = tables.sites.add_row(0.0, "A")
5858+
tables.mutations.add_row(site=s, node=leaf, derived_state="C") # id 0 (child)
5859+
tables.mutations.add_row(site=s, node=mid, derived_state="G") # id 1 (parent)
5860+
tables.sort()
5861+
mut_cols = tables.mutations.asdict()
5862+
mut_cols["parent"] = np.array([1, tskit.NULL], dtype=np.int32)
5863+
tables.mutations.set_columns(**mut_cols)
5864+
with pytest.raises(
5865+
tskit.LibraryError, match="TSK_ERR_MUTATION_PARENT_AFTER_CHILD"
5866+
):
5867+
tables.tree_sequence()
5868+
5869+
def test_tree_sequence_mutation_parent_different_site(self):
5870+
tables, a, _ = self._two_leaf_tree()
5871+
s0 = tables.sites.add_row(0.0, "A")
5872+
s1 = tables.sites.add_row(0.5, "A")
5873+
tables.mutations.add_row(site=s0, node=a, derived_state="C") # id 0
5874+
tables.mutations.add_row(site=s1, node=a, derived_state="G") # id 1
5875+
mut_cols = tables.mutations.asdict()
5876+
mut_cols["parent"] = np.array([tskit.NULL, 0], dtype=np.int32)
5877+
tables.mutations.set_columns(**mut_cols)
5878+
with pytest.raises(
5879+
tskit.LibraryError, match="TSK_ERR_MUTATION_PARENT_DIFFERENT_SITE"
5880+
):
5881+
tables.tree_sequence()
5882+
5883+
def test_tree_sequence_mutation_parent_equal(self):
5884+
tables, a, _ = self._two_leaf_tree()
5885+
s = tables.sites.add_row(0.0, "A")
5886+
tables.mutations.add_row(site=s, node=a, derived_state="C") # id 0
5887+
mut_cols = tables.mutations.asdict()
5888+
mut_cols["parent"] = np.array([0], dtype=np.int32)
5889+
tables.mutations.set_columns(**mut_cols)
5890+
with pytest.raises(tskit.LibraryError, match="TSK_ERR_MUTATION_PARENT_EQUAL"):
5891+
tables.tree_sequence()
5892+
5893+
def test_tree_sequence_mutation_parent_out_of_bounds(self):
5894+
tables, a, _ = self._two_leaf_tree()
5895+
s = tables.sites.add_row(0.0, "A")
5896+
tables.mutations.add_row(site=s, node=a, derived_state="C") # id 0
5897+
# >= num_rows
5898+
mut_cols = tables.mutations.asdict()
5899+
mut_cols["parent"] = np.array([1], dtype=np.int32)
5900+
tables.mutations.set_columns(**mut_cols)
5901+
with pytest.raises(tskit.LibraryError, match="TSK_ERR_MUTATION_OUT_OF_BOUNDS"):
5902+
tables.tree_sequence()
5903+
# < NULL
5904+
mut_cols = tables.mutations.asdict()
5905+
mut_cols["parent"] = np.array([-2], dtype=np.int32)
5906+
tables.mutations.set_columns(**mut_cols)
5907+
with pytest.raises(tskit.LibraryError, match="TSK_ERR_MUTATION_OUT_OF_BOUNDS"):
5908+
tables.tree_sequence()

0 commit comments

Comments
 (0)