xengsort 2.0.8__tar.gz → 2.1.0__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.8/xengsort.egg-info → xengsort-2.1.0}/PKG-INFO +18 -2
- {xengsort-2.0.8 → xengsort-2.1.0}/README.md +15 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/pyproject.toml +1 -1
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/cptasks_kmers.py +67 -23
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/dnaencode.py +14 -3
- xengsort-2.1.0/xengsort/dnaencode_fast.py +156 -0
- xengsort-2.1.0/xengsort/fastcash_info.py +156 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/fastcash_main.py +73 -50
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/hash_s3c_fbcbvb.py +18 -2
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/io/fastaio.py +11 -6
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/io/fastqio.py +206 -17
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/io/generalio.py +69 -14
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/io/hashio.py +142 -59
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/io/textio.py +0 -1
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/kmers.py +136 -10
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/lowlevel/bitarray.py +62 -12
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/lowlevel/conpro.py +20 -4
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/lowlevel/debug.py +41 -1
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/lowlevel/libc.py +30 -6
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/lowlevel/llvm.py +192 -1
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/srhash.py +97 -8
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/subtable_hashfunctions.py +12 -6
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/xengsort/config/classify.yaml +16 -1
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/xengsort/xengsort_classify.py +118 -19
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/xengsort/xengsort_index.py +5 -5
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/xengsort/xengsort_main.py +4 -0
- {xengsort-2.0.8 → xengsort-2.1.0/xengsort.egg-info}/PKG-INFO +18 -2
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort.egg-info/SOURCES.txt +1 -0
- xengsort-2.0.8/xengsort/fastcash_info.py +0 -83
- {xengsort-2.0.8 → xengsort-2.1.0}/LICENSE +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/MANIFEST.in +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/setup.cfg +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/tests/test_trimmed_reads.py +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/fastcash_optimize.py +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/hash_minimizer.py +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/hash_srh_fbcbvb.py +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/io/binaryio.py +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/io/seqio.py +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/io/xorio.py +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/lowlevel/aligned_arrays.py +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/lowlevel/intbitarray.py +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/lowlevel/numbautils.py +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/lowlevel/packedarray.py +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/mask.py +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/mathutils.py +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/parameters.py +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/values/xenograft.py +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/xengsort/config/index.yaml +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/xengsort/xengsort_info.py +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/xengsort/xengsort_load.py +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/xengsort/xengsort_remove.py +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort/xengsort/xengsort_weak.py +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort.egg-info/dependency_links.txt +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort.egg-info/entry_points.txt +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/xengsort.egg-info/requires.txt +0 -0
- {xengsort-2.0.8 → xengsort-2.1.0}/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.1.0
|
|
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
|
|
@@ -43,6 +43,7 @@ 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
|
|
|
@@ -286,6 +287,21 @@ Further parameters and options are:
|
|
|
286
287
|
- `--filter`: With this flag, only the graft reads are output.
|
|
287
288
|
- `--count`: With this flag, only the number of reads in each category is counted, but the reads are not sorted.
|
|
288
289
|
|
|
290
|
+
### How to filter bisulfite sequencing data
|
|
291
|
+
In bisulfite sequencing data we have a conversion from C to T and from G to A in the reverse complement. The standard xengsort index classifies most of the reads as `neither`.
|
|
292
|
+
To address this issue, we have created a `--bisulfite` mode.
|
|
293
|
+
This parameter must be set during the index building step.
|
|
294
|
+
If set, all Cs are translated to Ts in the reference and add the $k$-mers are added to the index, the same for G to A.
|
|
295
|
+
This nearly doubles the number of $k$-mers in the index (`-n`)
|
|
296
|
+
|
|
297
|
+
```
|
|
298
|
+
xengsort index --index myindex -H host.fa.gz -G graft.fa.gz -n 9_000_000_000 --bisulfite [OPTIONS]
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
If an index is build using the bisulfite parameter, also the read is converted in the classification step.
|
|
302
|
+
It is not necessary to change the parameters of the classification step.
|
|
303
|
+
Currently, it only supports the count-based classification.
|
|
304
|
+
|
|
289
305
|
|
|
290
306
|
Please report a [GitLab issue](https://gitlab.com/genomeinformatics/xengsort/-/issues) in case of problems.
|
|
291
307
|
|
|
@@ -240,6 +240,21 @@ Further parameters and options are:
|
|
|
240
240
|
- `--filter`: With this flag, only the graft reads are output.
|
|
241
241
|
- `--count`: With this flag, only the number of reads in each category is counted, but the reads are not sorted.
|
|
242
242
|
|
|
243
|
+
### How to filter bisulfite sequencing data
|
|
244
|
+
In bisulfite sequencing data we have a conversion from C to T and from G to A in the reverse complement. The standard xengsort index classifies most of the reads as `neither`.
|
|
245
|
+
To address this issue, we have created a `--bisulfite` mode.
|
|
246
|
+
This parameter must be set during the index building step.
|
|
247
|
+
If set, all Cs are translated to Ts in the reference and add the $k$-mers are added to the index, the same for G to A.
|
|
248
|
+
This nearly doubles the number of $k$-mers in the index (`-n`)
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
xengsort index --index myindex -H host.fa.gz -G graft.fa.gz -n 9_000_000_000 --bisulfite [OPTIONS]
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
If an index is build using the bisulfite parameter, also the read is converted in the classification step.
|
|
255
|
+
It is not necessary to change the parameters of the classification step.
|
|
256
|
+
Currently, it only supports the count-based classification.
|
|
257
|
+
|
|
243
258
|
|
|
244
259
|
Please report a [GitLab issue](https://gitlab.com/genomeinformatics/xengsort/-/issues) in case of problems.
|
|
245
260
|
|
|
@@ -4,15 +4,18 @@ Functions for compiling ConsumerProducerTasks for k-mers
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
|
-
from numba import njit, int32, int64, uint64
|
|
7
|
+
from numba import njit, uint8, 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 _TABLE_DNA_TO_2BITS, compile_quick_dna_to_2bits
|
|
11
|
+
from .dnaencode import quick_dna_to_2bits as quick_dna_to_2bits_old
|
|
12
|
+
from .dnaencode_fast import compile_revcomp_and_canonical_code as compile_revcomp_and_canonical_code_new
|
|
13
|
+
from .dnaencode_fast import quick_dna_to_2bits as quick_dna_to_2bits_new
|
|
10
14
|
from .lowlevel import debug
|
|
11
15
|
from .lowlevel.conpro import \
|
|
12
16
|
find_buffer_for_reading, find_buffer_for_writing, \
|
|
13
17
|
mark_buffer_for_reading, mark_buffer_for_writing, \
|
|
14
18
|
mark_my_buffers_failed
|
|
15
|
-
from .dnaencode import quick_dna_to_2bits
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
"""
|
|
@@ -28,7 +31,9 @@ compile_cptask_scatter_kmers_from_buffers():
|
|
|
28
31
|
|
|
29
32
|
def compile_cptask_scatter_kmers_from_linemarked(
|
|
30
33
|
mask, rcmode, hf0,
|
|
31
|
-
nsubtables, noutbuffers_per_subtable, outbufsize
|
|
34
|
+
nsubtables, noutbuffers_per_subtable, outbufsize,
|
|
35
|
+
new_encoding=False,
|
|
36
|
+
bisulfite=False):
|
|
32
37
|
"""
|
|
33
38
|
Compile a cptask function that
|
|
34
39
|
creates (gapped) k-mers from linemarked buffers.
|
|
@@ -38,16 +43,33 @@ def compile_cptask_scatter_kmers_from_linemarked(
|
|
|
38
43
|
- nsubtables: the number of subtables
|
|
39
44
|
- noutbuffers_per_subtable: number of output buffers per subtable
|
|
40
45
|
- outbufsize: the output buffer size for which to compile this function
|
|
46
|
+
-
|
|
41
47
|
"""
|
|
42
48
|
debugprint0, debugprint1, debugprint2 = debug.debugprint
|
|
43
49
|
both = (rcmode[0].lower() == "b")
|
|
50
|
+
if bisulfite:
|
|
51
|
+
if new_encoding:
|
|
52
|
+
raise NotImplementedError('WGBS with new encoding not supported')
|
|
53
|
+
|
|
54
|
+
else:
|
|
55
|
+
ct_table = _TABLE_DNA_TO_2BITS.copy()
|
|
56
|
+
ct_table[99] = 3
|
|
57
|
+
ct_table[67] = 3
|
|
58
|
+
quick_dna_to_2bits_ct = compile_quick_dna_to_2bits(ct_table)
|
|
59
|
+
|
|
60
|
+
ga_table = _TABLE_DNA_TO_2BITS.copy()
|
|
61
|
+
ga_table[103] = 0
|
|
62
|
+
ga_table[71] = 0
|
|
63
|
+
quick_dna_to_2bits_ga = compile_quick_dna_to_2bits(ga_table)
|
|
64
|
+
else:
|
|
65
|
+
quick_dna_to_2bits = quick_dna_to_2bits_new if new_encoding else quick_dna_to_2bits_old
|
|
44
66
|
|
|
45
67
|
k, w, tpl = mask.k, mask.w, mask.tuple
|
|
46
68
|
assert len(tpl) == k
|
|
47
69
|
if k < 1 or k > 32:
|
|
48
70
|
raise ValueError(f"Only 1 <= k <= 32 is supported, but {k=}.")
|
|
49
71
|
codemask = uint64(4**(k - 1) - 1)
|
|
50
|
-
revcomp, ccode =
|
|
72
|
+
revcomp, ccode = compile_revcomp_and_canonical_code_new(k, rcmode) if new_encoding else compile_revcomp_and_canonical_code_old(k, rcmode)
|
|
51
73
|
if mask.is_contiguous:
|
|
52
74
|
debugprint1(f"- compiling cptask_scatter_kmers for contiguous {k}-mers.")
|
|
53
75
|
else:
|
|
@@ -55,7 +77,7 @@ def compile_cptask_scatter_kmers_from_linemarked(
|
|
|
55
77
|
|
|
56
78
|
@njit(nogil=True, locals=dict(
|
|
57
79
|
code=uint64, st=uint64, sk=uint64,
|
|
58
|
-
j=int32, stj=int32, p=int32, wait=int64))
|
|
80
|
+
j=int32, stj=int32, p=int32, wait=int64, error=int32))
|
|
59
81
|
def _store(code, outidx, outpos, outbuffers, outcontrol):
|
|
60
82
|
st, sk = hf0(code)
|
|
61
83
|
j = outidx[st]
|
|
@@ -180,6 +202,8 @@ def compile_cptask_scatter_kmers_from_linemarked(
|
|
|
180
202
|
nactive = -1 # active input buffer
|
|
181
203
|
outidx = np.full(nsubtables, (-1), dtype=np.int32)
|
|
182
204
|
outpos = np.full(nsubtables, outbufsize, dtype=np.int32)
|
|
205
|
+
if bisulfite:
|
|
206
|
+
seq_buffer = np.empty(inbuffers[0].size, dtype=np.uint8)
|
|
183
207
|
while True:
|
|
184
208
|
nactive, wait = find_buffer_for_reading(incontrol, nactive)
|
|
185
209
|
wait_read += wait
|
|
@@ -193,10 +217,22 @@ def compile_cptask_scatter_kmers_from_linemarked(
|
|
|
193
217
|
errorcode = 0
|
|
194
218
|
linemarks = linemarkbuffers[nactive]
|
|
195
219
|
for i in range(nseqs):
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
220
|
+
if bisulfite:
|
|
221
|
+
seq_len = linemarks[i, 1] - linemarks[i, 0]
|
|
222
|
+
seq_buffer[0:seq_len] = active_buffer[linemarks[i, 0]:linemarks[i, 1]]
|
|
223
|
+
quick_dna_to_2bits_ct(seq_buffer[0:seq_len])
|
|
224
|
+
(wait, _errorcode) = _process(
|
|
225
|
+
seq_buffer[:seq_len], outidx, outpos, outbuffers, outcontrol)
|
|
226
|
+
|
|
227
|
+
seq_buffer[0:seq_len] = active_buffer[linemarks[i, 0]:linemarks[i, 1]]
|
|
228
|
+
quick_dna_to_2bits_ga(seq_buffer[0:seq_len])
|
|
229
|
+
(wait, _errorcode) = _process(
|
|
230
|
+
seq_buffer[:seq_len], outidx, outpos, outbuffers, outcontrol)
|
|
231
|
+
else:
|
|
232
|
+
sq = active_buffer[linemarks[i, 0]:linemarks[i, 1]]
|
|
233
|
+
quick_dna_to_2bits(sq)
|
|
234
|
+
(wait, _errorcode) = _process(
|
|
235
|
+
sq, outidx, outpos, outbuffers, outcontrol)
|
|
200
236
|
wait_write += wait
|
|
201
237
|
errorcode |= _errorcode
|
|
202
238
|
if errorcode:
|
|
@@ -229,6 +265,7 @@ def compile_cptask_scatter_kmers_from_linemarked(
|
|
|
229
265
|
if j >= 0:
|
|
230
266
|
# assert outpos[st] > 0
|
|
231
267
|
outcontrol[j, 7] = outpos[st]
|
|
268
|
+
outcontrol[j, 6] = 1 # buffer type k-mer codes
|
|
232
269
|
mark_buffer_for_reading(outcontrol, j)
|
|
233
270
|
debugprint2("- ending: cptask_scatter_kmers", errorcode)
|
|
234
271
|
return (wait_read, wait_write, -errorcode)
|
|
@@ -238,7 +275,7 @@ def compile_cptask_scatter_kmers_from_linemarked(
|
|
|
238
275
|
|
|
239
276
|
def compile_cptask_scatter_kmers_position_from_linemarked(
|
|
240
277
|
mask, values, rcmode, hf0,
|
|
241
|
-
nsubtables, noutbuffers_per_subtable, outbufsize):
|
|
278
|
+
nsubtables, noutbuffers_per_subtable, outbufsize, new_encoding=False):
|
|
242
279
|
"""
|
|
243
280
|
Compile a cptask function that
|
|
244
281
|
creates (gapped) k-mers and value from linemarked buffers.
|
|
@@ -251,14 +288,16 @@ def compile_cptask_scatter_kmers_position_from_linemarked(
|
|
|
251
288
|
- outbufsize: the output buffer size for which to compile this function
|
|
252
289
|
"""
|
|
253
290
|
debugprint0, debugprint1, debugprint2 = debug.debugprint
|
|
291
|
+
dassert = debug.dassert
|
|
254
292
|
both = (rcmode[0].lower() == "b")
|
|
293
|
+
quick_dna_to_2bits = quick_dna_to_2bits_new if new_encoding else quick_dna_to_2bits_old
|
|
255
294
|
|
|
256
295
|
k, w, tpl = mask.k, mask.w, mask.tuple
|
|
257
296
|
assert len(tpl) == k
|
|
258
297
|
if k < 1 or k > 32:
|
|
259
298
|
raise ValueError(f"Only 1 <= k <= 32 is supported, but {k=}.")
|
|
260
299
|
codemask = uint64(4**(k - 1) - 1)
|
|
261
|
-
revcomp, ccode =
|
|
300
|
+
revcomp, ccode = compile_revcomp_and_canonical_code_new(k, rcmode) if new_encoding else compile_revcomp_and_canonical_code_old(k, rcmode)
|
|
262
301
|
if mask.is_contiguous:
|
|
263
302
|
debugprint1(f"- compiling cptask_scatter_kmers for contiguous {k}-mers.")
|
|
264
303
|
else:
|
|
@@ -281,9 +320,9 @@ def compile_cptask_scatter_kmers_position_from_linemarked(
|
|
|
281
320
|
outcontrol[j, 6] = 1 # buffer type k-mer codes
|
|
282
321
|
mark_buffer_for_reading(outcontrol, j)
|
|
283
322
|
stj = j - st * noutbuffers_per_subtable # the "local" j for subtable st
|
|
284
|
-
|
|
323
|
+
dassert(0 <= stj < noutbuffers_per_subtable, __name__, '0 <= stj < noutbuffers_per_subtable', (stj, noutbuffers_per_subtable))
|
|
285
324
|
else:
|
|
286
|
-
|
|
325
|
+
dassert(j == -1, __name__, 'j == -1', j)
|
|
287
326
|
stj = -1
|
|
288
327
|
stoutcontrol = outcontrol[(st * noutbuffers_per_subtable):(st + 1) * noutbuffers_per_subtable]
|
|
289
328
|
stj, wait = find_buffer_for_writing(stoutcontrol, stj)
|
|
@@ -382,14 +421,15 @@ def compile_cptask_scatter_kmers_position_from_linemarked(
|
|
|
382
421
|
# ininfos[i]: linemarks for input buffer i
|
|
383
422
|
# outcontrol[B, 7]: k-mers in buffer B
|
|
384
423
|
debugprint2("- running: cptask_scatter_kmers; outbuffers shape =", outbuffers.shape)
|
|
385
|
-
|
|
386
|
-
|
|
424
|
+
dassert(inbuffers.ndim == 2, __name__, 'inbuffers.ndim == 2', inbuffers.ndim, message='check input buffer dimensions')
|
|
425
|
+
dassert(outbuffers.ndim == 2, __name__, 'outbuffers.ndim == 2', outbuffers.ndim, message='check output buffer dimensions')
|
|
426
|
+
|
|
387
427
|
M, N = ininfos.shape
|
|
388
|
-
|
|
428
|
+
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
429
|
linemarkbuffers = ininfos.reshape(M, N // 6, 6)
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
430
|
+
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')
|
|
431
|
+
dassert(outbuffers.shape[1] >= outbufsize, __name__, 'outbuffers.shape[1] >= outbufsize', (outbuffers.shape[1], outbufsize))
|
|
432
|
+
dassert(outinfos.shape[1] == 0, __name__, 'outinfos.shape[1] == 0', outinfos.shape[1])
|
|
393
433
|
|
|
394
434
|
wait_read = wait_write = 0
|
|
395
435
|
nactive = -1 # active input buffer
|
|
@@ -404,7 +444,7 @@ def compile_cptask_scatter_kmers_position_from_linemarked(
|
|
|
404
444
|
active_buffer = inbuffers[nactive]
|
|
405
445
|
nseqs = incontrol[nactive, 7]
|
|
406
446
|
buffer_type = incontrol[nactive, 6]
|
|
407
|
-
|
|
447
|
+
dassert(buffer_type == 0, __name__, 'buffer_type == 0', buffer_type, message='0 means sequence buffer')
|
|
408
448
|
|
|
409
449
|
errorcode = 0
|
|
410
450
|
linemarks = linemarkbuffers[nactive]
|
|
@@ -430,6 +470,7 @@ def compile_cptask_scatter_kmers_position_from_linemarked(
|
|
|
430
470
|
if j >= 0:
|
|
431
471
|
# assert outpos[st] > 0
|
|
432
472
|
outcontrol[j, 7] = outpos[st]
|
|
473
|
+
outcontrol[j, 6] = 1 # buffer type k-mer codes
|
|
433
474
|
mark_buffer_for_reading(outcontrol, j)
|
|
434
475
|
debugprint2("- ending: cptask_scatter_kmers", errorcode)
|
|
435
476
|
return (wait_read, wait_write, -errorcode)
|
|
@@ -461,7 +502,6 @@ def compile_cptask_insert_subkey_value(
|
|
|
461
502
|
assert outbuffers.shape[1] >= maxwalk + 1
|
|
462
503
|
assert outinfos.shape[1] == 0
|
|
463
504
|
debugprint2("- running: cptask_insert_subkey_value; subtable", st)
|
|
464
|
-
|
|
465
505
|
wait_read = wait_write = 0
|
|
466
506
|
nactive = -1 # active input buffer
|
|
467
507
|
active_buffer = inbuffers[0] # irrelevant
|
|
@@ -506,6 +546,7 @@ def compile_cptask_insert_filtered_subkeys(
|
|
|
506
546
|
maxfailures=0,
|
|
507
547
|
maxwalk=1000):
|
|
508
548
|
debugprint0, debugprint1, debugprint2 = debug.debugprint
|
|
549
|
+
dassert = debug.dassert
|
|
509
550
|
if myfilter is not None:
|
|
510
551
|
wwo = "with"
|
|
511
552
|
lookup_and_insert_in_subfilter = myfilter.private.lookup_and_insert_in_subfilter
|
|
@@ -552,7 +593,10 @@ def compile_cptask_insert_filtered_subkeys(
|
|
|
552
593
|
active_buffer = inbuffers[nactive]
|
|
553
594
|
nsubkeys = incontrol[nactive, 7]
|
|
554
595
|
buffer_type = incontrol[nactive, 6]
|
|
555
|
-
|
|
596
|
+
if buffer_type != 1:
|
|
597
|
+
print(buffer_type, nsubkeys)
|
|
598
|
+
assert buffer_type == 1
|
|
599
|
+
# dassert(buffer_type == 1, __name__, 'buffer_type == 1', buffer_type, message='0 means sequence buffer')
|
|
556
600
|
|
|
557
601
|
# consume the buffer
|
|
558
602
|
for subkey in active_buffer[:nsubkeys]:
|
|
@@ -34,6 +34,15 @@ def _dna_to_2bits(x, table):
|
|
|
34
34
|
x[i] = table[x[i]]
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
def compile_quick_dna_to_2bits(table):
|
|
38
|
+
@njit(nogil=True)
|
|
39
|
+
def _quick_dna_to_2bits(x):
|
|
40
|
+
for i in range(len(x)):
|
|
41
|
+
x[i] = table[x[i]]
|
|
42
|
+
|
|
43
|
+
return _quick_dna_to_2bits
|
|
44
|
+
|
|
45
|
+
# this is the one to use!
|
|
37
46
|
@njit(nogil=True)
|
|
38
47
|
def quick_dna_to_2bits(x):
|
|
39
48
|
for i in range(len(x)):
|
|
@@ -51,7 +60,7 @@ def parallel_dna_to_2bits(x):
|
|
|
51
60
|
def dna_to_2bits(seq, table=_TABLE_DNA_TO_2BITS):
|
|
52
61
|
# we expect seq to be a bytearray
|
|
53
62
|
# xx = np.array(seq, dtype=np.uint8)
|
|
54
|
-
if
|
|
63
|
+
if isinstance(seq, bytes):
|
|
55
64
|
xx = np.frombuffer(bytearray(seq), dtype=np.uint8)
|
|
56
65
|
else:
|
|
57
66
|
xx = np.frombuffer(seq, dtype=np.uint8)
|
|
@@ -70,7 +79,7 @@ def qcode_to_dnastr(qcode, q, table=_TABLE_BITS_TO_DNASTR):
|
|
|
70
79
|
@njit(nogil=True, locals=dict(base=uint64))
|
|
71
80
|
def write_qcode_to_buffer(qcode, q, buf, start):
|
|
72
81
|
for i in range(q):
|
|
73
|
-
base = qcode >> (2 * (q - i - 1)) & 3
|
|
82
|
+
base = (qcode >> (2 * (q - i - 1))) & 3
|
|
74
83
|
buf[start + i] = uint8(base)
|
|
75
84
|
|
|
76
85
|
|
|
@@ -245,6 +254,7 @@ def compile_twobit_to_codes(tmask, rcmode, invalid=U64_MINUSONE):
|
|
|
245
254
|
|
|
246
255
|
@njit(nogil=True, locals=dict(code=uint64))
|
|
247
256
|
def twobit_to_codes(seq, out, start=0, n=-1):
|
|
257
|
+
"""write n (or all) canonical k-mer codes from seq[start...] into out buffer"""
|
|
248
258
|
if n == -1:
|
|
249
259
|
n = len(seq) - w + 1 - start
|
|
250
260
|
for i in range(start, start + n):
|
|
@@ -260,7 +270,8 @@ def compile_twobit_to_codes(tmask, rcmode, invalid=U64_MINUSONE):
|
|
|
260
270
|
out[i - start] = code
|
|
261
271
|
|
|
262
272
|
@njit(nogil=True, locals=dict(code=uint64))
|
|
263
|
-
def twobit_to_code(seq, start):
|
|
273
|
+
def twobit_to_code(seq, start=0):
|
|
274
|
+
"""return a single canonical code at seq[start...]"""
|
|
264
275
|
code = 0
|
|
265
276
|
for j in tmask:
|
|
266
277
|
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(nogil=True)
|
|
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(nogil=True)
|
|
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(nogil=True, 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(nogil=True, 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")
|