Skip to content

Commit 2b85302

Browse files
Pass num_alleles through to MatcherIndexes on construction
Add num_alleles parameter to matcher_indexes_alloc in C; if NULL, defaults to biallelic (2). Thread through Python/C binding and MatcherIndexes wrapper. Fixes TSI_ERR_BAD_HAPLOTYPE_ALLELE on multi-allelic sites. Add triallelic C test.
1 parent a3972aa commit 2b85302

6 files changed

Lines changed: 75 additions & 21 deletions

File tree

_tsinfermodule.c

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1683,29 +1683,42 @@ MatcherIndexes_init(MatcherIndexes *self, PyObject *args, PyObject *kwds)
16831683
int ret = -1;
16841684
int err;
16851685
LightweightTableCollection *tables;
1686-
static char *kwlist[] = { "tables", NULL };
1686+
PyObject *num_alleles_obj = NULL;
1687+
PyArrayObject *num_alleles_array = NULL;
1688+
tsk_size_t *num_alleles_data = NULL;
1689+
static char *kwlist[] = { "tables", "num_alleles", NULL };
16871690

16881691
self->matcher_indexes = NULL;
1689-
if (!PyArg_ParseTupleAndKeywords(
1690-
args, kwds, "O!", kwlist, &LightweightTableCollectionType, &tables)) {
1692+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!|O", kwlist,
1693+
&LightweightTableCollectionType, &tables, &num_alleles_obj)) {
16911694
goto out;
16921695
}
16931696
if (LightweightTableCollection_check_state(tables) != 0) {
16941697
goto out;
16951698
}
1699+
if (num_alleles_obj != NULL && num_alleles_obj != Py_None) {
1700+
num_alleles_array = (PyArrayObject *) PyArray_FROMANY(
1701+
num_alleles_obj, NPY_UINT64, 1, 1, NPY_ARRAY_IN_ARRAY);
1702+
if (num_alleles_array == NULL) {
1703+
goto out;
1704+
}
1705+
num_alleles_data = PyArray_DATA(num_alleles_array);
1706+
}
16961707

16971708
self->matcher_indexes = PyMem_Calloc(1, sizeof(*self->matcher_indexes));
16981709
if (self->matcher_indexes == NULL) {
16991710
PyErr_NoMemory();
17001711
goto out;
17011712
}
1702-
err = matcher_indexes_alloc(self->matcher_indexes, tables->tables, 0);
1713+
err = matcher_indexes_alloc(
1714+
self->matcher_indexes, tables->tables, num_alleles_data, 0);
17031715
if (err != 0) {
17041716
handle_library_error(err);
17051717
goto out;
17061718
}
17071719
ret = 0;
17081720
out:
1721+
Py_XDECREF(num_alleles_array);
17091722
return ret;
17101723
}
17111724

lib/ancestor_matcher.c

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1108,8 +1108,8 @@ matcher_indexes_add_mutation(
11081108
}
11091109

11101110
static int
1111-
matcher_indexes_copy_mutation_data(
1112-
matcher_indexes_t *self, const tsk_table_collection_t *tables)
1111+
matcher_indexes_copy_mutation_data(matcher_indexes_t *self,
1112+
const tsk_table_collection_t *tables, const tsk_size_t *num_alleles)
11131113
{
11141114
int ret = 0;
11151115
tsk_size_t j;
@@ -1124,7 +1124,7 @@ matcher_indexes_copy_mutation_data(
11241124
/* TODO check for under/overflow */
11251125
converted_position[j] = (coordinate_t) sites_position[j];
11261126
self->sites.mutations[j] = NULL;
1127-
self->sites.num_alleles[j] = 2;
1127+
self->sites.num_alleles[j] = num_alleles != NULL ? num_alleles[j] : 2;
11281128
}
11291129
converted_position[j] = (coordinate_t) tables->sequence_length;
11301130

@@ -1137,8 +1137,6 @@ matcher_indexes_copy_mutation_data(
11371137
}
11381138

11391139
self->sites.mutations[site] = NULL;
1140-
/* FIXME */
1141-
self->sites.num_alleles[site] = 2;
11421140
ret = matcher_indexes_add_mutation(self, site, mutations_node[j], 1);
11431141
if (ret != 0) {
11441142
goto out;
@@ -1151,8 +1149,8 @@ matcher_indexes_copy_mutation_data(
11511149
}
11521150

11531151
int
1154-
matcher_indexes_alloc(
1155-
matcher_indexes_t *self, const tsk_table_collection_t *tables, tsk_flags_t flags)
1152+
matcher_indexes_alloc(matcher_indexes_t *self, const tsk_table_collection_t *tables,
1153+
const tsk_size_t *num_alleles, tsk_flags_t flags)
11561154
{
11571155
int ret = 0;
11581156

@@ -1185,7 +1183,7 @@ matcher_indexes_alloc(
11851183
if (ret != 0) {
11861184
goto out;
11871185
}
1188-
ret = matcher_indexes_copy_mutation_data(self, tables);
1186+
ret = matcher_indexes_copy_mutation_data(self, tables, num_alleles);
11891187
if (ret != 0) {
11901188
goto out;
11911189
}

lib/tests/tests.c

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1082,7 +1082,7 @@ run_match(const tsk_treeseq_t *ts, double rho, double mu, const allele_t *h,
10821082
recombination_rate[j] = rho;
10831083
}
10841084

1085-
ret = matcher_indexes_alloc(&mi, ts->tables, 0);
1085+
ret = matcher_indexes_alloc(&mi, ts->tables, NULL, 0);
10861086
CU_ASSERT_EQUAL_FATAL(ret, 0);
10871087
/* matcher_indexes_print_state(&mi, stdout); */
10881088
ret = ancestor_matcher2_alloc(&am, &mi, recombination_rate, mutation_rate, 14, 0);
@@ -1441,6 +1441,44 @@ test_matching_deep_chain_ts(void)
14411441
tsk_treeseq_free(&ts);
14421442
}
14431443

1444+
static void
1445+
test_matching_triallelic_ts(void)
1446+
{
1447+
/*
1448+
* Test that num_alleles > 2 is accepted.
1449+
* Use the star topology but tell the matcher that site 0 has 3 alleles.
1450+
* Query with allele 2 at site 0 should not trigger BAD_HAPLOTYPE_ALLELE.
1451+
*/
1452+
tsk_treeseq_t ts;
1453+
ancestor_matcher2_t am;
1454+
matcher_indexes_t mi;
1455+
allele_t match[3];
1456+
tsk_id_t left[3], right[3], parent[3];
1457+
tsk_size_t path_length;
1458+
tsk_size_t num_alleles[] = { 3, 2, 2 };
1459+
double rho[] = { 1e-9, 1e-9, 1e-9 };
1460+
double mu[] = { 1e-20, 1e-20, 1e-20 };
1461+
int ret;
1462+
1463+
build_star_ts(&ts);
1464+
1465+
ret = matcher_indexes_alloc(&mi, ts.tables, num_alleles, 0);
1466+
CU_ASSERT_EQUAL_FATAL(ret, 0);
1467+
ret = ancestor_matcher2_alloc(&am, &mi, rho, mu, 14, 0);
1468+
CU_ASSERT_EQUAL_FATAL(ret, 0);
1469+
1470+
/* Query [2,0,0]: allele 2 at site 0 needs num_alleles >= 3 */
1471+
allele_t h[] = { 2, 0, 0 };
1472+
ret = ancestor_matcher2_find_path(
1473+
&am, 0, 3, h, match, &path_length, left, right, parent);
1474+
CU_ASSERT_EQUAL_FATAL(ret, 0);
1475+
CU_ASSERT_EQUAL(path_length, 1);
1476+
1477+
ancestor_matcher2_free(&am);
1478+
matcher_indexes_free(&mi);
1479+
tsk_treeseq_free(&ts);
1480+
}
1481+
14441482
static void
14451483
test_strerror(void)
14461484
{
@@ -1563,6 +1601,7 @@ main(int argc, char **argv)
15631601
{ "test_matching_binary_ts", test_matching_binary_ts },
15641602
{ "test_matching_two_tree_ts", test_matching_two_tree_ts },
15651603
{ "test_matching_deep_chain_ts", test_matching_deep_chain_ts },
1604+
{ "test_matching_triallelic_ts", test_matching_triallelic_ts },
15661605

15671606
{ "test_strerror", test_strerror },
15681607

lib/tsinfer.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,8 +358,8 @@ int tree_sequence_builder_dump_mutations(tree_sequence_builder_t *self, tsk_id_t
358358

359359
/* New impelementation */
360360

361-
int matcher_indexes_alloc(
362-
matcher_indexes_t *self, const tsk_table_collection_t *tables, tsk_flags_t options);
361+
int matcher_indexes_alloc(matcher_indexes_t *self, const tsk_table_collection_t *tables,
362+
const tsk_size_t *num_alleles, tsk_flags_t options);
363363
int matcher_indexes_print_state(const matcher_indexes_t *self, FILE *out);
364364
int matcher_indexes_free(matcher_indexes_t *self);
365365

tests/test_lshmm.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ class MatcherIndexes:
7575
The memory that can be shared between AncestorMatcher instances.
7676
"""
7777

78-
def __init__(self, in_tables, *, vestigial_root=True):
78+
def __init__(self, in_tables, *, vestigial_root=True, num_alleles=None):
7979
ts = in_tables.tree_sequence()
8080
if vestigial_root:
8181
ts = matching.add_vestigial_root(ts)
@@ -93,8 +93,10 @@ def __init__(self, in_tables, *, vestigial_root=True):
9393
tables.edges, tables.indexes.edge_removal_order
9494
)
9595

96-
# TODO fixme
97-
self.num_alleles = np.zeros(self.num_sites, dtype=int) + 2
96+
if num_alleles is not None:
97+
self.num_alleles = np.asarray(num_alleles, dtype=int)
98+
else:
99+
self.num_alleles = np.zeros(self.num_sites, dtype=int) + 2
98100
self.sites_position = np.zeros(ts.num_sites + 1, dtype=np.uint32)
99101
self.sites_position[:-1] = tables.sites.position
100102
self.sites_position[-1] = tables.sequence_length

tsinfer/matching.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,9 @@ def __init__(
264264
)
265265
t0 = time_.monotonic()
266266
if self._use_matcher2:
267-
self._matcher_indexes = MatcherIndexes(ts, vestigial_root=False)
267+
self._matcher_indexes = MatcherIndexes(
268+
ts, vestigial_root=False, num_alleles=num_alleles
269+
)
268270
else:
269271
self._tsb = _tsb_from_ts(
270272
ts,
@@ -551,15 +553,15 @@ def mean_traceback_size(self):
551553
class MatcherIndexes(_tsinfer.MatcherIndexes):
552554
"""Wrapper around the C MatcherIndexes, built from a tree sequence."""
553555

554-
def __init__(self, ts, *, vestigial_root=True):
556+
def __init__(self, ts, *, vestigial_root=True, num_alleles=None):
555557
if vestigial_root:
556558
ts = add_vestigial_root(ts)
557559
tables = ts.dump_tables()
558560
ll_tables = _tsinfer.LightweightTableCollection(tables.sequence_length)
559561
ll_tables.fromdict(tables.asdict())
560562
self.sequence_length = ts.sequence_length
561563
self.num_sites = ts.num_sites
562-
super().__init__(ll_tables)
564+
super().__init__(ll_tables, num_alleles=num_alleles)
563565

564566

565567
class AncestorMatcher2(_tsinfer.AncestorMatcher2):

0 commit comments

Comments
 (0)