xengsort 2.0.7__tar.gz → 2.0.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {xengsort-2.0.7 → xengsort-2.0.9}/MANIFEST.in +1 -1
- {xengsort-2.0.7/xengsort.egg-info → xengsort-2.0.9}/PKG-INFO +5 -4
- {xengsort-2.0.7 → xengsort-2.0.9}/pyproject.toml +3 -3
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/cptasks_kmers.py +29 -18
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/dnaencode.py +6 -3
- xengsort-2.0.9/xengsort/dnaencode_fast.py +156 -0
- xengsort-2.0.9/xengsort/fastcash_info.py +156 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/fastcash_main.py +71 -42
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/hash_s3c_fbcbvb.py +2 -2
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/io/fastaio.py +11 -6
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/io/fastqio.py +180 -9
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/io/generalio.py +56 -13
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/io/hashio.py +140 -67
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/io/textio.py +0 -1
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/kmers.py +13 -9
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/lowlevel/bitarray.py +1 -5
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/lowlevel/conpro.py +20 -4
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/lowlevel/debug.py +39 -1
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/lowlevel/libc.py +30 -6
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/lowlevel/llvm.py +14 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/srhash.py +6 -4
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/subtable_hashfunctions.py +12 -6
- xengsort-2.0.9/xengsort/xengsort/config/classify.yaml +60 -0
- xengsort-2.0.9/xengsort/xengsort/config/index.yaml +9 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/xengsort/xengsort_classify.py +13 -12
- {xengsort-2.0.7 → xengsort-2.0.9/xengsort.egg-info}/PKG-INFO +5 -4
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort.egg-info/SOURCES.txt +4 -1
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort.egg-info/requires.txt +2 -2
- xengsort-2.0.7/xengsort/fastcash_info.py +0 -83
- {xengsort-2.0.7 → xengsort-2.0.9}/LICENSE +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/README.md +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/setup.cfg +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/tests/test_trimmed_reads.py +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/fastcash_optimize.py +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/hash_minimizer.py +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/hash_srh_fbcbvb.py +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/io/binaryio.py +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/io/seqio.py +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/io/xorio.py +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/lowlevel/aligned_arrays.py +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/lowlevel/intbitarray.py +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/lowlevel/numbautils.py +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/lowlevel/packedarray.py +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/mask.py +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/mathutils.py +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/parameters.py +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/values/xenograft.py +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/xengsort/xengsort_index.py +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/xengsort/xengsort_info.py +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/xengsort/xengsort_load.py +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/xengsort/xengsort_main.py +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/xengsort/xengsort_remove.py +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort/xengsort/xengsort_weak.py +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort.egg-info/dependency_links.txt +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort.egg-info/entry_points.txt +0 -0
- {xengsort-2.0.7 → xengsort-2.0.9}/xengsort.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: xengsort
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.9
|
|
4
4
|
Summary: xenograft sorting of FASTQ reads into five categories: host, graft, both, neither, ambiguous.
|
|
5
5
|
Author-email: Jens Zentgraf <zentgraf@cs.uni-saarland.de>, Sven Rahmann <rahmann@cs.uni-saarland.de>
|
|
6
6
|
License: MIT License
|
|
@@ -38,11 +38,12 @@ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
|
38
38
|
Requires-Python: >=3.12
|
|
39
39
|
Description-Content-Type: text/markdown
|
|
40
40
|
License-File: LICENSE
|
|
41
|
-
Requires-Dist: numpy
|
|
42
|
-
Requires-Dist: numba
|
|
41
|
+
Requires-Dist: numpy
|
|
42
|
+
Requires-Dist: numba
|
|
43
43
|
Requires-Dist: pytest
|
|
44
44
|
Requires-Dist: jsonargparse>=4.29.0
|
|
45
45
|
Requires-Dist: pip
|
|
46
|
+
Dynamic: license-file
|
|
46
47
|
|
|
47
48
|
# xengsort: Fast lightweight accurate xenograft sorting
|
|
48
49
|
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
|
|
6
6
|
[project]
|
|
7
7
|
name = "xengsort"
|
|
8
|
-
version = "2.0.
|
|
8
|
+
version = "2.0.9"
|
|
9
9
|
|
|
10
10
|
license = {file = "LICENSE"}
|
|
11
11
|
|
|
@@ -21,8 +21,8 @@ readme = "README.md"
|
|
|
21
21
|
requires-python = ">=3.12"
|
|
22
22
|
|
|
23
23
|
dependencies = [
|
|
24
|
-
"numpy
|
|
25
|
-
"numba
|
|
24
|
+
"numpy",
|
|
25
|
+
"numba",
|
|
26
26
|
"pytest",
|
|
27
27
|
"jsonargparse>=4.29.0",
|
|
28
28
|
"pip",
|
|
@@ -6,13 +6,15 @@ Functions for compiling ConsumerProducerTasks for k-mers
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
from numba import njit, int32, int64, uint64
|
|
8
8
|
|
|
9
|
-
from .dnaencode import compile_revcomp_and_canonical_code
|
|
9
|
+
from .dnaencode import compile_revcomp_and_canonical_code as compile_revcomp_and_canonical_code_old
|
|
10
|
+
from .dnaencode import quick_dna_to_2bits as quick_dna_to_2bits_old
|
|
11
|
+
from .dnaencode_fast import compile_revcomp_and_canonical_code as compile_revcomp_and_canonical_code_new
|
|
12
|
+
from .dnaencode_fast import quick_dna_to_2bits as quick_dna_to_2bits_new
|
|
10
13
|
from .lowlevel import debug
|
|
11
14
|
from .lowlevel.conpro import \
|
|
12
15
|
find_buffer_for_reading, find_buffer_for_writing, \
|
|
13
16
|
mark_buffer_for_reading, mark_buffer_for_writing, \
|
|
14
17
|
mark_my_buffers_failed
|
|
15
|
-
from .dnaencode import quick_dna_to_2bits
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
"""
|
|
@@ -28,7 +30,7 @@ compile_cptask_scatter_kmers_from_buffers():
|
|
|
28
30
|
|
|
29
31
|
def compile_cptask_scatter_kmers_from_linemarked(
|
|
30
32
|
mask, rcmode, hf0,
|
|
31
|
-
nsubtables, noutbuffers_per_subtable, outbufsize):
|
|
33
|
+
nsubtables, noutbuffers_per_subtable, outbufsize, new_encoding=False):
|
|
32
34
|
"""
|
|
33
35
|
Compile a cptask function that
|
|
34
36
|
creates (gapped) k-mers from linemarked buffers.
|
|
@@ -41,13 +43,14 @@ def compile_cptask_scatter_kmers_from_linemarked(
|
|
|
41
43
|
"""
|
|
42
44
|
debugprint0, debugprint1, debugprint2 = debug.debugprint
|
|
43
45
|
both = (rcmode[0].lower() == "b")
|
|
46
|
+
quick_dna_to_2bits = quick_dna_to_2bits_new if new_encoding else quick_dna_to_2bits_old
|
|
44
47
|
|
|
45
48
|
k, w, tpl = mask.k, mask.w, mask.tuple
|
|
46
49
|
assert len(tpl) == k
|
|
47
50
|
if k < 1 or k > 32:
|
|
48
51
|
raise ValueError(f"Only 1 <= k <= 32 is supported, but {k=}.")
|
|
49
52
|
codemask = uint64(4**(k - 1) - 1)
|
|
50
|
-
revcomp, ccode =
|
|
53
|
+
revcomp, ccode = compile_revcomp_and_canonical_code_new(k, rcmode) if new_encoding else compile_revcomp_and_canonical_code_old(k, rcmode)
|
|
51
54
|
if mask.is_contiguous:
|
|
52
55
|
debugprint1(f"- compiling cptask_scatter_kmers for contiguous {k}-mers.")
|
|
53
56
|
else:
|
|
@@ -55,7 +58,7 @@ def compile_cptask_scatter_kmers_from_linemarked(
|
|
|
55
58
|
|
|
56
59
|
@njit(nogil=True, locals=dict(
|
|
57
60
|
code=uint64, st=uint64, sk=uint64,
|
|
58
|
-
j=int32, stj=int32, p=int32, wait=int64))
|
|
61
|
+
j=int32, stj=int32, p=int32, wait=int64, error=int32))
|
|
59
62
|
def _store(code, outidx, outpos, outbuffers, outcontrol):
|
|
60
63
|
st, sk = hf0(code)
|
|
61
64
|
j = outidx[st]
|
|
@@ -229,6 +232,7 @@ def compile_cptask_scatter_kmers_from_linemarked(
|
|
|
229
232
|
if j >= 0:
|
|
230
233
|
# assert outpos[st] > 0
|
|
231
234
|
outcontrol[j, 7] = outpos[st]
|
|
235
|
+
outcontrol[j, 6] = 1 # buffer type k-mer codes
|
|
232
236
|
mark_buffer_for_reading(outcontrol, j)
|
|
233
237
|
debugprint2("- ending: cptask_scatter_kmers", errorcode)
|
|
234
238
|
return (wait_read, wait_write, -errorcode)
|
|
@@ -238,7 +242,7 @@ def compile_cptask_scatter_kmers_from_linemarked(
|
|
|
238
242
|
|
|
239
243
|
def compile_cptask_scatter_kmers_position_from_linemarked(
|
|
240
244
|
mask, values, rcmode, hf0,
|
|
241
|
-
nsubtables, noutbuffers_per_subtable, outbufsize):
|
|
245
|
+
nsubtables, noutbuffers_per_subtable, outbufsize, new_encoding=False):
|
|
242
246
|
"""
|
|
243
247
|
Compile a cptask function that
|
|
244
248
|
creates (gapped) k-mers and value from linemarked buffers.
|
|
@@ -251,14 +255,16 @@ def compile_cptask_scatter_kmers_position_from_linemarked(
|
|
|
251
255
|
- outbufsize: the output buffer size for which to compile this function
|
|
252
256
|
"""
|
|
253
257
|
debugprint0, debugprint1, debugprint2 = debug.debugprint
|
|
258
|
+
dassert = debug.dassert
|
|
254
259
|
both = (rcmode[0].lower() == "b")
|
|
260
|
+
quick_dna_to_2bits = quick_dna_to_2bits_new if new_encoding else quick_dna_to_2bits_old
|
|
255
261
|
|
|
256
262
|
k, w, tpl = mask.k, mask.w, mask.tuple
|
|
257
263
|
assert len(tpl) == k
|
|
258
264
|
if k < 1 or k > 32:
|
|
259
265
|
raise ValueError(f"Only 1 <= k <= 32 is supported, but {k=}.")
|
|
260
266
|
codemask = uint64(4**(k - 1) - 1)
|
|
261
|
-
revcomp, ccode =
|
|
267
|
+
revcomp, ccode = compile_revcomp_and_canonical_code_new(k, rcmode) if new_encoding else compile_revcomp_and_canonical_code_old(k, rcmode)
|
|
262
268
|
if mask.is_contiguous:
|
|
263
269
|
debugprint1(f"- compiling cptask_scatter_kmers for contiguous {k}-mers.")
|
|
264
270
|
else:
|
|
@@ -281,9 +287,9 @@ def compile_cptask_scatter_kmers_position_from_linemarked(
|
|
|
281
287
|
outcontrol[j, 6] = 1 # buffer type k-mer codes
|
|
282
288
|
mark_buffer_for_reading(outcontrol, j)
|
|
283
289
|
stj = j - st * noutbuffers_per_subtable # the "local" j for subtable st
|
|
284
|
-
|
|
290
|
+
dassert(0 <= stj < noutbuffers_per_subtable, __name__, '0 <= stj < noutbuffers_per_subtable', (stj, noutbuffers_per_subtable))
|
|
285
291
|
else:
|
|
286
|
-
|
|
292
|
+
dassert(j == -1, __name__, 'j == -1', j)
|
|
287
293
|
stj = -1
|
|
288
294
|
stoutcontrol = outcontrol[(st * noutbuffers_per_subtable):(st + 1) * noutbuffers_per_subtable]
|
|
289
295
|
stj, wait = find_buffer_for_writing(stoutcontrol, stj)
|
|
@@ -382,14 +388,15 @@ def compile_cptask_scatter_kmers_position_from_linemarked(
|
|
|
382
388
|
# ininfos[i]: linemarks for input buffer i
|
|
383
389
|
# outcontrol[B, 7]: k-mers in buffer B
|
|
384
390
|
debugprint2("- running: cptask_scatter_kmers; outbuffers shape =", outbuffers.shape)
|
|
385
|
-
|
|
386
|
-
|
|
391
|
+
dassert(inbuffers.ndim == 2, __name__, 'inbuffers.ndim == 2', inbuffers.ndim, message='check input buffer dimensions')
|
|
392
|
+
dassert(outbuffers.ndim == 2, __name__, 'outbuffers.ndim == 2', outbuffers.ndim, message='check output buffer dimensions')
|
|
393
|
+
|
|
387
394
|
M, N = ininfos.shape
|
|
388
|
-
|
|
395
|
+
dassert(N % 6 == 0, __name__, 'N % 6 == 0', N, message='We have 6 entries per linemark. Start and end of record and read and chromosome and positions')
|
|
389
396
|
linemarkbuffers = ininfos.reshape(M, N // 6, 6)
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
397
|
+
dassert(outbuffers.shape[0] == nsubtables * noutbuffers_per_subtable, __name__, 'outbuffers.shape[0] == nsubtables * noutbuffers_per_subtable', (outbuffers.shape[0], nsubtables, noutbuffers_per_subtable), message='Check number of output buffer per subtable')
|
|
398
|
+
dassert(outbuffers.shape[1] >= outbufsize, __name__, 'outbuffers.shape[1] >= outbufsize', (outbuffers.shape[1], outbufsize))
|
|
399
|
+
dassert(outinfos.shape[1] == 0, __name__, 'outinfos.shape[1] == 0', outinfos.shape[1])
|
|
393
400
|
|
|
394
401
|
wait_read = wait_write = 0
|
|
395
402
|
nactive = -1 # active input buffer
|
|
@@ -404,7 +411,7 @@ def compile_cptask_scatter_kmers_position_from_linemarked(
|
|
|
404
411
|
active_buffer = inbuffers[nactive]
|
|
405
412
|
nseqs = incontrol[nactive, 7]
|
|
406
413
|
buffer_type = incontrol[nactive, 6]
|
|
407
|
-
|
|
414
|
+
dassert(buffer_type == 0, __name__, 'buffer_type == 0', buffer_type, message='0 means sequence buffer')
|
|
408
415
|
|
|
409
416
|
errorcode = 0
|
|
410
417
|
linemarks = linemarkbuffers[nactive]
|
|
@@ -430,6 +437,7 @@ def compile_cptask_scatter_kmers_position_from_linemarked(
|
|
|
430
437
|
if j >= 0:
|
|
431
438
|
# assert outpos[st] > 0
|
|
432
439
|
outcontrol[j, 7] = outpos[st]
|
|
440
|
+
outcontrol[j, 6] = 1 # buffer type k-mer codes
|
|
433
441
|
mark_buffer_for_reading(outcontrol, j)
|
|
434
442
|
debugprint2("- ending: cptask_scatter_kmers", errorcode)
|
|
435
443
|
return (wait_read, wait_write, -errorcode)
|
|
@@ -461,7 +469,6 @@ def compile_cptask_insert_subkey_value(
|
|
|
461
469
|
assert outbuffers.shape[1] >= maxwalk + 1
|
|
462
470
|
assert outinfos.shape[1] == 0
|
|
463
471
|
debugprint2("- running: cptask_insert_subkey_value; subtable", st)
|
|
464
|
-
|
|
465
472
|
wait_read = wait_write = 0
|
|
466
473
|
nactive = -1 # active input buffer
|
|
467
474
|
active_buffer = inbuffers[0] # irrelevant
|
|
@@ -506,6 +513,7 @@ def compile_cptask_insert_filtered_subkeys(
|
|
|
506
513
|
maxfailures=0,
|
|
507
514
|
maxwalk=1000):
|
|
508
515
|
debugprint0, debugprint1, debugprint2 = debug.debugprint
|
|
516
|
+
dassert = debug.dassert
|
|
509
517
|
if myfilter is not None:
|
|
510
518
|
wwo = "with"
|
|
511
519
|
lookup_and_insert_in_subfilter = myfilter.private.lookup_and_insert_in_subfilter
|
|
@@ -552,7 +560,10 @@ def compile_cptask_insert_filtered_subkeys(
|
|
|
552
560
|
active_buffer = inbuffers[nactive]
|
|
553
561
|
nsubkeys = incontrol[nactive, 7]
|
|
554
562
|
buffer_type = incontrol[nactive, 6]
|
|
555
|
-
|
|
563
|
+
if buffer_type != 1:
|
|
564
|
+
print(buffer_type, nsubkeys)
|
|
565
|
+
assert buffer_type == 1
|
|
566
|
+
# dassert(buffer_type == 1, __name__, 'buffer_type == 1', buffer_type, message='0 means sequence buffer')
|
|
556
567
|
|
|
557
568
|
# consume the buffer
|
|
558
569
|
for subkey in active_buffer[:nsubkeys]:
|
|
@@ -34,6 +34,7 @@ def _dna_to_2bits(x, table):
|
|
|
34
34
|
x[i] = table[x[i]]
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
# this is the one to use!
|
|
37
38
|
@njit(nogil=True)
|
|
38
39
|
def quick_dna_to_2bits(x):
|
|
39
40
|
for i in range(len(x)):
|
|
@@ -51,7 +52,7 @@ def parallel_dna_to_2bits(x):
|
|
|
51
52
|
def dna_to_2bits(seq, table=_TABLE_DNA_TO_2BITS):
|
|
52
53
|
# we expect seq to be a bytearray
|
|
53
54
|
# xx = np.array(seq, dtype=np.uint8)
|
|
54
|
-
if
|
|
55
|
+
if isinstance(seq, bytes):
|
|
55
56
|
xx = np.frombuffer(bytearray(seq), dtype=np.uint8)
|
|
56
57
|
else:
|
|
57
58
|
xx = np.frombuffer(seq, dtype=np.uint8)
|
|
@@ -70,7 +71,7 @@ def qcode_to_dnastr(qcode, q, table=_TABLE_BITS_TO_DNASTR):
|
|
|
70
71
|
@njit(nogil=True, locals=dict(base=uint64))
|
|
71
72
|
def write_qcode_to_buffer(qcode, q, buf, start):
|
|
72
73
|
for i in range(q):
|
|
73
|
-
base = qcode >> (2 * (q - i - 1)) & 3
|
|
74
|
+
base = (qcode >> (2 * (q - i - 1))) & 3
|
|
74
75
|
buf[start + i] = uint8(base)
|
|
75
76
|
|
|
76
77
|
|
|
@@ -245,6 +246,7 @@ def compile_twobit_to_codes(tmask, rcmode, invalid=U64_MINUSONE):
|
|
|
245
246
|
|
|
246
247
|
@njit(nogil=True, locals=dict(code=uint64))
|
|
247
248
|
def twobit_to_codes(seq, out, start=0, n=-1):
|
|
249
|
+
"""write n (or all) canonical k-mer codes from seq[start...] into out buffer"""
|
|
248
250
|
if n == -1:
|
|
249
251
|
n = len(seq) - w + 1 - start
|
|
250
252
|
for i in range(start, start + n):
|
|
@@ -260,7 +262,8 @@ def compile_twobit_to_codes(tmask, rcmode, invalid=U64_MINUSONE):
|
|
|
260
262
|
out[i - start] = code
|
|
261
263
|
|
|
262
264
|
@njit(nogil=True, locals=dict(code=uint64))
|
|
263
|
-
def twobit_to_code(seq, start):
|
|
265
|
+
def twobit_to_code(seq, start=0):
|
|
266
|
+
"""return a single canonical code at seq[start...]"""
|
|
264
267
|
code = 0
|
|
265
268
|
for j in tmask:
|
|
266
269
|
c = seq[start + j]
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Fast DNA encoding and computation of the reverse complement A=00, C=01, T=10, G=11, U=10
|
|
3
|
+
Other characters are not treated correctly, except N=7>3 and n = 6>3
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from numba import njit, uint64
|
|
7
|
+
import numpy as np
|
|
8
|
+
from .mask import Mask
|
|
9
|
+
|
|
10
|
+
U64_MINUSONE = uint64(np.iinfo(np.uint64).max)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@njit
|
|
14
|
+
def quick_dna_to_2bits(x):
|
|
15
|
+
for i in range(len(x)):
|
|
16
|
+
x[i] = (x[i] >> 1) & 7
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@njit
|
|
20
|
+
def dna_to_2bits(x, y):
|
|
21
|
+
for i in range(len(x)):
|
|
22
|
+
y[i] = (x[i] >> 1) & 7
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# no need to njit!
|
|
26
|
+
def _get_table_2bits_to_dna():
|
|
27
|
+
b = np.full(256, 35, dtype=np.uint8) # fill with b'#'
|
|
28
|
+
b[0] = 65
|
|
29
|
+
b[1] = 67
|
|
30
|
+
b[2] = 84
|
|
31
|
+
b[3] = 71
|
|
32
|
+
b[6] = 78
|
|
33
|
+
b[7] = 78
|
|
34
|
+
return b
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
_TABLE_2BITS_TO_DNA = _get_table_2bits_to_dna()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@njit(nogil=True)
|
|
41
|
+
def twobits_to_dna_inplace(buf, start=0, end=0):
|
|
42
|
+
if end <= 0:
|
|
43
|
+
end = len(buf) - end
|
|
44
|
+
for i in range(start, end):
|
|
45
|
+
buf[i] = _TABLE_2BITS_TO_DNA[buf[i]]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
_TABLE_BITS_TO_DNASTR = ["A", "C", "T", "G"]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def qcode_to_dnastr(qcode, q, table=_TABLE_BITS_TO_DNASTR):
|
|
52
|
+
qc = int(qcode)
|
|
53
|
+
return "".join([table[((qc >> (2 * (q - i - 1))) & 3)] for i in range(q)])
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def compile_revcomp_and_canonical_code(q, rcmode):
|
|
57
|
+
"""
|
|
58
|
+
return pair of functions (revcomp_code_q, canonical_code_q)
|
|
59
|
+
specialized for q-gram codes for the given value of q.
|
|
60
|
+
It is expected that LLVM optimization does loop unrolling.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
mask = uint64(int(q * '10', 2))
|
|
64
|
+
shift = uint64(64 - 2 * q)
|
|
65
|
+
|
|
66
|
+
@njit(locals=dict(value=uint64))
|
|
67
|
+
def reverse_in_pairs(value):
|
|
68
|
+
value = ((value & 0x3333333333333333) << 2) | ((value & 0xCCCCCCCCCCCCCCCC) >> 2)
|
|
69
|
+
value = ((value & 0x0F0F0F0F0F0F0F0F) << 4) | ((value >> 4) & 0x0F0F0F0F0F0F0F0F)
|
|
70
|
+
value = ((value & 0x00FF00FF00FF00FF) << 8) | ((value >> 8) & 0x00FF00FF00FF00FF)
|
|
71
|
+
value = ((value & 0x0000FFFF0000FFFF) << 16) | ((value >> 16) & 0x0000FFFF0000FFFF)
|
|
72
|
+
value = (value << 32) | (value >> 32)
|
|
73
|
+
return value
|
|
74
|
+
|
|
75
|
+
@njit(locals=dict(value=uint64))
|
|
76
|
+
def _rc(value):
|
|
77
|
+
value ^= mask
|
|
78
|
+
value = reverse_in_pairs(value)
|
|
79
|
+
return value >> shift
|
|
80
|
+
|
|
81
|
+
if rcmode == "min":
|
|
82
|
+
@njit(nogil=True, locals=dict(
|
|
83
|
+
code=uint64, rc=uint64))
|
|
84
|
+
def _cc(code):
|
|
85
|
+
rc = _rc(code)
|
|
86
|
+
return code if code <= rc else rc
|
|
87
|
+
elif rcmode == "max":
|
|
88
|
+
@njit(nogil=True, locals=dict(
|
|
89
|
+
code=uint64, rc=uint64))
|
|
90
|
+
def _cc(code):
|
|
91
|
+
rc = _rc(code)
|
|
92
|
+
return code if code >= rc else rc
|
|
93
|
+
elif rcmode == "r":
|
|
94
|
+
@njit(nogil=True, locals=dict(
|
|
95
|
+
code=uint64, rc=uint64))
|
|
96
|
+
def _cc(code):
|
|
97
|
+
rc = _rc(code)
|
|
98
|
+
return rc
|
|
99
|
+
else: # 'f', 'both', ...
|
|
100
|
+
@njit(nogil=True, locals=dict(
|
|
101
|
+
code=uint64, rc=uint64))
|
|
102
|
+
def _cc(code):
|
|
103
|
+
return code
|
|
104
|
+
|
|
105
|
+
return _rc, _cc
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# translation of a DNA buffer into codes
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def compile_twobit_to_codes(tmask, rcmode, invalid=U64_MINUSONE):
|
|
112
|
+
# tmask should be a mask tuple, but might be an int in some cases
|
|
113
|
+
if isinstance(tmask, int):
|
|
114
|
+
k = w = tmask # be safe here
|
|
115
|
+
tmask = tuple(range(k))
|
|
116
|
+
elif isinstance(tmask, Mask):
|
|
117
|
+
k = tmask.k
|
|
118
|
+
w = tmask.w
|
|
119
|
+
tmask = tmask.tuple
|
|
120
|
+
elif type(tmask) is tuple:
|
|
121
|
+
k, w = len(tmask), tmask[-1] + 1
|
|
122
|
+
else:
|
|
123
|
+
raise ValueError(f"mask type {type(tmask)} is not supported.")
|
|
124
|
+
_, ccc = compile_revcomp_and_canonical_code(k, rcmode)
|
|
125
|
+
|
|
126
|
+
@njit(nogil=True, locals=dict(code=uint64))
|
|
127
|
+
def twobit_to_codes(seq, out, start=0, n=-1):
|
|
128
|
+
"""write n (or all) canonical k-mer codes from seq[start...] into out buffer"""
|
|
129
|
+
if n == -1:
|
|
130
|
+
n = len(seq) - w + 1 - start
|
|
131
|
+
for i in range(start, start + n):
|
|
132
|
+
code = 0
|
|
133
|
+
for j in tmask:
|
|
134
|
+
c = seq[i + j]
|
|
135
|
+
if c >= 4:
|
|
136
|
+
out[i - start] = uint64(invalid)
|
|
137
|
+
break
|
|
138
|
+
code = (code << 2) | c
|
|
139
|
+
else:
|
|
140
|
+
code = ccc(code)
|
|
141
|
+
out[i - start] = code
|
|
142
|
+
|
|
143
|
+
@njit(nogil=True, locals=dict(code=uint64))
|
|
144
|
+
def twobit_to_code(seq, start=0):
|
|
145
|
+
"""return a single canonical code at seq[start...]"""
|
|
146
|
+
code = 0
|
|
147
|
+
for j in tmask:
|
|
148
|
+
c = seq[start + j]
|
|
149
|
+
if c >= 4:
|
|
150
|
+
return uint64(invalid)
|
|
151
|
+
code = (code << 2) | c
|
|
152
|
+
else:
|
|
153
|
+
code = ccc(code)
|
|
154
|
+
return code
|
|
155
|
+
|
|
156
|
+
return twobit_to_codes, twobit_to_code
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""
|
|
2
|
+
fastcash_info.py:
|
|
3
|
+
Collect and print information on a hash table stored on disk.
|
|
4
|
+
Select elements according to a filter functions;
|
|
5
|
+
export selected or all elements (keys, values, choices, info).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from os import sep as SLASH
|
|
9
|
+
from concurrent.futures import ThreadPoolExecutor, wait
|
|
10
|
+
from importlib.util import spec_from_file_location, module_from_spec
|
|
11
|
+
|
|
12
|
+
from numba import njit
|
|
13
|
+
|
|
14
|
+
from .lowlevel import debug
|
|
15
|
+
from .srhash import print_statistics
|
|
16
|
+
from .io.hashio import load_hash, export_hash
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def print_dict(d, title=None):
|
|
20
|
+
if title is not None:
|
|
21
|
+
print(f"\n### {title}")
|
|
22
|
+
for key, value in d.items():
|
|
23
|
+
print(f"- {key}: {value}")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def print_values(values, valuetup, title=None):
|
|
27
|
+
if title is not None:
|
|
28
|
+
print(f"\n### {title}")
|
|
29
|
+
print(f"- value set specification: {valuetup}")
|
|
30
|
+
print(f"- value set name: {values.NAME}")
|
|
31
|
+
print(f"- value set nvalues: {values.NVALUES}")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def compile_filter_function(
|
|
35
|
+
expr=None, compile=None,
|
|
36
|
+
valueset=None, appinfo=None):
|
|
37
|
+
# any filter given?
|
|
38
|
+
if expr is None and compile is None:
|
|
39
|
+
f = (lambda key, choice, value: True)
|
|
40
|
+
ff = njit(nogil=True)(f)
|
|
41
|
+
return ff, False
|
|
42
|
+
# filter by expression in key, choice, value
|
|
43
|
+
elif expr is not None:
|
|
44
|
+
# expr must be an expression using 'key', 'choice', 'value' and constants.
|
|
45
|
+
f = eval(f'lambda key, choice, value: ({expr})')
|
|
46
|
+
ff = njit(nogil=True)(f)
|
|
47
|
+
return ff, True
|
|
48
|
+
# filter by compiling a function, given by name='modulepath::funcname'.
|
|
49
|
+
# compile must be a list: [name, *params] (all strings).
|
|
50
|
+
# name must be of the form "module.function"
|
|
51
|
+
# This function receives the valueset and appinfo as constants,
|
|
52
|
+
# and must return a compiled function f(key, choice, value).
|
|
53
|
+
name, params = compile[0], compile[1:]
|
|
54
|
+
module_path, func = map(str.strip, name.split("::"))
|
|
55
|
+
*_, module_name = module_path.split(SLASH)
|
|
56
|
+
module_path += ".py"
|
|
57
|
+
_spec = spec_from_file_location(module_name, module_path)
|
|
58
|
+
module = module_from_spec(_spec)
|
|
59
|
+
_spec.loader.exec_module(module)
|
|
60
|
+
cf = getattr(module, func)
|
|
61
|
+
ff = cf(valueset=valueset, appinfo=appinfo, params=params)
|
|
62
|
+
return ff, True
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def compile_count_filtered(h, filterfunc):
|
|
66
|
+
hp = h.private
|
|
67
|
+
is_slot_empty_at = hp.is_slot_empty_at
|
|
68
|
+
get_item_at = hp.get_item_at
|
|
69
|
+
get_subkey_choice_from_bucket_signature = hp.get_subkey_choice_from_bucket_signature
|
|
70
|
+
get_key_from_subtable_subkey = hp.get_key_from_subtable_subkey
|
|
71
|
+
nbuckets, bucketsize = h.nbuckets, h.bucketsize
|
|
72
|
+
|
|
73
|
+
@njit(nogil=True)
|
|
74
|
+
def count_filtered(ht, st):
|
|
75
|
+
n = total = 0
|
|
76
|
+
for bucket in range(nbuckets):
|
|
77
|
+
for slot in range(bucketsize):
|
|
78
|
+
if is_slot_empty_at(ht, st, bucket, slot):
|
|
79
|
+
break # go to next bucket
|
|
80
|
+
sig, val = get_item_at(ht, st, bucket, slot)
|
|
81
|
+
sbk, chc = get_subkey_choice_from_bucket_signature(bucket, sig)
|
|
82
|
+
key = get_key_from_subtable_subkey(st, sbk)
|
|
83
|
+
n += filterfunc(key, chc, val)
|
|
84
|
+
total += 1
|
|
85
|
+
return n, total
|
|
86
|
+
|
|
87
|
+
return count_filtered
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def run_filter(h, filterfunc):
|
|
91
|
+
count_filtered = compile_count_filtered(h, filterfunc)
|
|
92
|
+
nsubtables = h.nsubtables if hasattr(h, "nsubtables") else h.subtables
|
|
93
|
+
with ThreadPoolExecutor(max_workers=nsubtables) as executor:
|
|
94
|
+
futures = [
|
|
95
|
+
executor.submit(count_filtered, h.hashtable, st)
|
|
96
|
+
for st in range(nsubtables)
|
|
97
|
+
]
|
|
98
|
+
wait(futures)
|
|
99
|
+
filtered = total = 0
|
|
100
|
+
for st, f in enumerate(futures):
|
|
101
|
+
sgood, stotal = f.result() # this may raise errors
|
|
102
|
+
filtered += sgood
|
|
103
|
+
total += stotal
|
|
104
|
+
return filtered, total
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def main(args):
|
|
108
|
+
global debugprint0, debugprint1, debugprint2
|
|
109
|
+
global timestamp0, timestamp1, timestamp2
|
|
110
|
+
debugprint0, debugprint1, debugprint2 = debug.debugprint
|
|
111
|
+
timestamp0, timestamp1, timestamp2 = debug.timestamp
|
|
112
|
+
starttime = timestamp0(msg="\n# fastcash info/export : information and export of fastcash hash tables")
|
|
113
|
+
|
|
114
|
+
# load hash table information
|
|
115
|
+
_, values, infotup = load_hash(args.hashname, info_only=True)
|
|
116
|
+
(hashinfo, valueinfo, optinfo, appinfo) = infotup
|
|
117
|
+
print_dict(hashinfo, "Hash table properties")
|
|
118
|
+
print_dict(optinfo)
|
|
119
|
+
print_dict(appinfo)
|
|
120
|
+
print_values(values, valueinfo, "Value set properties")
|
|
121
|
+
|
|
122
|
+
# count number of k-mers passing the filter
|
|
123
|
+
filterfunc, runfilter = compile_filter_function(
|
|
124
|
+
expr=args.filterexpression,
|
|
125
|
+
compile=args.compilefilter,
|
|
126
|
+
valueset=values, appinfo=appinfo)
|
|
127
|
+
|
|
128
|
+
# load the hash table and print statistics
|
|
129
|
+
h, values, infotup = load_hash(args.hashname)
|
|
130
|
+
nvalues = values.NVALUES
|
|
131
|
+
SV = {'all': nvalues, 'none': False}
|
|
132
|
+
sv = args.showvalues
|
|
133
|
+
sv = SV[sv] if sv in SV else int(sv)
|
|
134
|
+
print_statistics(h, level=args.statistics, show_values=sv)
|
|
135
|
+
|
|
136
|
+
# run a compiled filter
|
|
137
|
+
if runfilter:
|
|
138
|
+
filtered, total = run_filter(h, filterfunc)
|
|
139
|
+
percent = filtered / total if total > 0 else 0.0
|
|
140
|
+
debugprint0(f"\n- k-mers passing filter: {filtered} of {total}, or {percent:.2%}")
|
|
141
|
+
|
|
142
|
+
# export data
|
|
143
|
+
fmt = args.format # native, packed, text/txt, dna
|
|
144
|
+
anyformat = (fmt is not None)
|
|
145
|
+
prefix = args.outprefix
|
|
146
|
+
if (prefix is None) and anyformat:
|
|
147
|
+
prefix = args.hashname
|
|
148
|
+
if prefix is not None:
|
|
149
|
+
startout = timestamp0(msg="\n## Data Output")
|
|
150
|
+
fmt = {None: 'native', 'txt': 'text'}.get(fmt, fmt)
|
|
151
|
+
written = export_hash(h, nvalues, infotup, prefix, fmt, filterfunc)
|
|
152
|
+
debugprint0(f"- wrote {written} items")
|
|
153
|
+
timestamp0(startout, msg="- done writing data; writing time")
|
|
154
|
+
|
|
155
|
+
# that's it; successfully exit the program
|
|
156
|
+
timestamp0(starttime, msg="- DONE; total time")
|