Skip to content

Commit b2d74dc

Browse files
committed
Add tests for bad mutation parents
1 parent 8e6b1e6 commit b2d74dc

File tree

1 file changed

+130
-0
lines changed

1 file changed

+130
-0
lines changed

python/tests/test_tables.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3568,6 +3568,53 @@ 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(tables.mutations.parent, np.array([tskit.NULL, tskit.NULL]))
3612+
3613+
# Explicit loop in parents should be ignored by compute_mutation_parents
3614+
tables.mutations.parent[:] = np.array([1, 0], dtype=np.int32)
3615+
tables.compute_mutation_parents()
3616+
assert np.array_equal(tables.mutations.parent, np.array([tskit.NULL, tskit.NULL]))
3617+
35713618
def test_str(self):
35723619
ts = msprime.simulate(10, random_seed=1)
35733620
tables = ts.tables
@@ -5768,3 +5815,86 @@ def test_ragged_selection_indices_non_monotonic():
57685815
gather = _ragged_selection_indices(indexed_offsets, lengths64)
57695816
expected = np.array([5, 0, 1], dtype=np.int64)
57705817
assert np.array_equal(gather, expected)
5818+
5819+
5820+
class TestMutationParentValidation:
5821+
def _two_leaf_tree(self):
5822+
tables = tskit.TableCollection(sequence_length=1.0)
5823+
root = tables.nodes.add_row(time=2.0)
5824+
a = tables.nodes.add_row(time=0, flags=tskit.NODE_IS_SAMPLE)
5825+
b = tables.nodes.add_row(time=0, flags=tskit.NODE_IS_SAMPLE)
5826+
tables.edges.add_row(0.0, 1.0, root, a)
5827+
tables.edges.add_row(0.0, 1.0, root, b)
5828+
return tables, a, b
5829+
5830+
def _chain_tree(self):
5831+
tables = tskit.TableCollection(sequence_length=1.0)
5832+
root = tables.nodes.add_row(time=2.0)
5833+
mid = tables.nodes.add_row(time=1.0)
5834+
leaf = tables.nodes.add_row(time=0, flags=tskit.NODE_IS_SAMPLE)
5835+
tables.edges.add_row(0.0, 1.0, root, mid)
5836+
tables.edges.add_row(0.0, 1.0, mid, leaf)
5837+
return tables, mid, leaf
5838+
5839+
def test_tree_sequence_bad_mutation_parent_topology(self):
5840+
tables, a, b = self._two_leaf_tree()
5841+
s = tables.sites.add_row(0.0, "A")
5842+
tables.mutations.add_row(site=s, node=a, derived_state="C") # id 0
5843+
tables.mutations.add_row(site=s, node=b, derived_state="G") # id 1
5844+
# Make a mutation on a parallel branch the parent
5845+
mut_cols = tables.mutations.asdict()
5846+
mut_cols["parent"] = np.array([tskit.NULL, 0], dtype=np.int32)
5847+
tables.mutations.set_columns(**mut_cols)
5848+
with pytest.raises(tskit.LibraryError, match="TSK_ERR_BAD_MUTATION_PARENT"):
5849+
tables.tree_sequence()
5850+
5851+
def test_tree_sequence_mutation_parent_after_child(self):
5852+
tables, mid, leaf = self._chain_tree()
5853+
s = tables.sites.add_row(0.0, "A")
5854+
tables.mutations.add_row(site=s, node=leaf, derived_state="C") # id 0 (child)
5855+
tables.mutations.add_row(site=s, node=mid, derived_state="G") # id 1 (parent)
5856+
tables.sort()
5857+
mut_cols = tables.mutations.asdict()
5858+
mut_cols["parent"] = np.array([1, tskit.NULL], dtype=np.int32)
5859+
tables.mutations.set_columns(**mut_cols)
5860+
with pytest.raises(tskit.LibraryError, match="TSK_ERR_MUTATION_PARENT_AFTER_CHILD"):
5861+
tables.tree_sequence()
5862+
5863+
def test_tree_sequence_mutation_parent_different_site(self):
5864+
tables, a, _ = self._two_leaf_tree()
5865+
s0 = tables.sites.add_row(0.0, "A")
5866+
s1 = tables.sites.add_row(0.5, "A")
5867+
tables.mutations.add_row(site=s0, node=a, derived_state="C") # id 0
5868+
tables.mutations.add_row(site=s1, node=a, derived_state="G") # id 1
5869+
mut_cols = tables.mutations.asdict()
5870+
mut_cols["parent"] = np.array([tskit.NULL, 0], dtype=np.int32)
5871+
tables.mutations.set_columns(**mut_cols)
5872+
with pytest.raises(tskit.LibraryError, match="TSK_ERR_MUTATION_PARENT_DIFFERENT_SITE"):
5873+
tables.tree_sequence()
5874+
5875+
def test_tree_sequence_mutation_parent_equal(self):
5876+
tables, a, _ = self._two_leaf_tree()
5877+
s = tables.sites.add_row(0.0, "A")
5878+
tables.mutations.add_row(site=s, node=a, derived_state="C") # id 0
5879+
mut_cols = tables.mutations.asdict()
5880+
mut_cols["parent"] = np.array([0], dtype=np.int32)
5881+
tables.mutations.set_columns(**mut_cols)
5882+
with pytest.raises(tskit.LibraryError, match="TSK_ERR_MUTATION_PARENT_EQUAL"):
5883+
tables.tree_sequence()
5884+
5885+
def test_tree_sequence_mutation_parent_out_of_bounds(self):
5886+
tables, a, _ = self._two_leaf_tree()
5887+
s = tables.sites.add_row(0.0, "A")
5888+
tables.mutations.add_row(site=s, node=a, derived_state="C") # id 0
5889+
# >= num_rows
5890+
mut_cols = tables.mutations.asdict()
5891+
mut_cols["parent"] = np.array([1], dtype=np.int32)
5892+
tables.mutations.set_columns(**mut_cols)
5893+
with pytest.raises(tskit.LibraryError, match="TSK_ERR_MUTATION_OUT_OF_BOUNDS"):
5894+
tables.tree_sequence()
5895+
# < NULL
5896+
mut_cols = tables.mutations.asdict()
5897+
mut_cols["parent"] = np.array([-2], dtype=np.int32)
5898+
tables.mutations.set_columns(**mut_cols)
5899+
with pytest.raises(tskit.LibraryError, match="TSK_ERR_MUTATION_OUT_OF_BOUNDS"):
5900+
tables.tree_sequence()

0 commit comments

Comments
 (0)