xengsort 2.1.1__tar.gz → 2.2.1__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.1.1/xengsort.egg-info → xengsort-2.2.1}/PKG-INFO +1 -1
- {xengsort-2.1.1 → xengsort-2.2.1}/pyproject.toml +1 -1
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/cptasks_kmers.py +4 -4
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/fastcash_main.py +5 -4
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/hash_s3c_fbcbvb.py +1 -1
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/io/fastqio.py +259 -9
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/kmers.py +133 -3
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/lowlevel/libc.py +7 -5
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/lowlevel/llvm.py +31 -4
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/srhash.py +1 -1
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/xengsort/config/classify.yaml +1 -1
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/xengsort/xengsort_classify.py +245 -79
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/xengsort/xengsort_index.py +5 -5
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/xengsort/xengsort_main.py +4 -2
- {xengsort-2.1.1 → xengsort-2.2.1/xengsort.egg-info}/PKG-INFO +1 -1
- {xengsort-2.1.1 → xengsort-2.2.1}/LICENSE +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/MANIFEST.in +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/README.md +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/setup.cfg +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/tests/test_trimmed_reads.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/dnaencode.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/dnaencode_fast.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/fastcash_info.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/fastcash_optimize.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/hash_minimizer.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/hash_srh_fbcbvb.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/io/binaryio.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/io/fastaio.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/io/generalio.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/io/hashio.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/io/seqio.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/io/textio.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/io/xorio.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/lowlevel/aligned_arrays.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/lowlevel/bitarray.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/lowlevel/conpro.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/lowlevel/debug.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/lowlevel/intbitarray.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/lowlevel/numbautils.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/lowlevel/packedarray.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/mask.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/mathutils.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/parameters.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/subtable_hashfunctions.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/values/xenograft.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/xengsort/config/index.yaml +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/xengsort/xengsort_info.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/xengsort/xengsort_load.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/xengsort/xengsort_remove.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/xengsort/xengsort_weak.py +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort.egg-info/SOURCES.txt +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort.egg-info/dependency_links.txt +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort.egg-info/entry_points.txt +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort.egg-info/requires.txt +0 -0
- {xengsort-2.1.1 → xengsort-2.2.1}/xengsort.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xengsort
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.1
|
|
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
|
|
@@ -33,7 +33,7 @@ def compile_cptask_scatter_kmers_from_linemarked(
|
|
|
33
33
|
mask, rcmode, hf0,
|
|
34
34
|
nsubtables, noutbuffers_per_subtable, outbufsize,
|
|
35
35
|
new_encoding=False,
|
|
36
|
-
|
|
36
|
+
methylation=False):
|
|
37
37
|
"""
|
|
38
38
|
Compile a cptask function that
|
|
39
39
|
creates (gapped) k-mers from linemarked buffers.
|
|
@@ -47,7 +47,7 @@ def compile_cptask_scatter_kmers_from_linemarked(
|
|
|
47
47
|
"""
|
|
48
48
|
debugprint0, debugprint1, debugprint2 = debug.debugprint
|
|
49
49
|
both = (rcmode[0].lower() == "b")
|
|
50
|
-
if
|
|
50
|
+
if methylation:
|
|
51
51
|
if new_encoding:
|
|
52
52
|
raise NotImplementedError('WGBS with new encoding not supported')
|
|
53
53
|
|
|
@@ -202,7 +202,7 @@ def compile_cptask_scatter_kmers_from_linemarked(
|
|
|
202
202
|
nactive = -1 # active input buffer
|
|
203
203
|
outidx = np.full(nsubtables, (-1), dtype=np.int32)
|
|
204
204
|
outpos = np.full(nsubtables, outbufsize, dtype=np.int32)
|
|
205
|
-
if
|
|
205
|
+
if methylation:
|
|
206
206
|
seq_buffer = np.empty(inbuffers[0].size, dtype=np.uint8)
|
|
207
207
|
while True:
|
|
208
208
|
nactive, wait = find_buffer_for_reading(incontrol, nactive)
|
|
@@ -217,7 +217,7 @@ def compile_cptask_scatter_kmers_from_linemarked(
|
|
|
217
217
|
errorcode = 0
|
|
218
218
|
linemarks = linemarkbuffers[nactive]
|
|
219
219
|
for i in range(nseqs):
|
|
220
|
-
if
|
|
220
|
+
if methylation:
|
|
221
221
|
seq_len = linemarks[i, 1] - linemarks[i, 0]
|
|
222
222
|
seq_buffer[0:seq_len] = active_buffer[linemarks[i, 0]:linemarks[i, 1]]
|
|
223
223
|
quick_dna_to_2bits_ct(seq_buffer[0:seq_len])
|
|
@@ -7,6 +7,7 @@ from argparse import ArgumentParser
|
|
|
7
7
|
from importlib import import_module # dynamically import subcommand
|
|
8
8
|
from importlib.metadata import metadata
|
|
9
9
|
from os.path import split as splitpath, splitext
|
|
10
|
+
from collections import defaultdict
|
|
10
11
|
|
|
11
12
|
from .lowlevel.debug import set_debugfunctions
|
|
12
13
|
|
|
@@ -28,7 +29,7 @@ def add_hash_table_arguments(p: ArgumentParser):
|
|
|
28
29
|
help="bucket size, i.e. number of elements on a bucket")
|
|
29
30
|
p.add_argument("--fill", type=float, default="0.9",
|
|
30
31
|
help="desired fill rate of the hash table")
|
|
31
|
-
p.add_argument("--subtables", type=int, default=5,
|
|
32
|
+
p.add_argument("--subtables", type=int, default=5,
|
|
32
33
|
help="number of subtables used; subtables+1 threads are used")
|
|
33
34
|
|
|
34
35
|
|
|
@@ -51,20 +52,20 @@ def add_hash_arguments(p: ArgumentParser):
|
|
|
51
52
|
help="number of objects to be stored")
|
|
52
53
|
p.add_argument("--type", default="default", # default="3c_fbcbvb",
|
|
53
54
|
help="hash type (e.g. [s]3c_fbcbvb, 2v_fbcbvb), implemented in hash_{TYPE}.py")
|
|
54
|
-
p.add_argument("--unaligned", action="store_const",
|
|
55
|
+
p.add_argument("--unaligned", action="store_const",
|
|
55
56
|
const=False, dest="aligned", default=None,
|
|
56
57
|
help="use unaligned buckets (smaller, slightly slower; default)")
|
|
57
58
|
p.add_argument("--aligned", action="store_const",
|
|
58
59
|
const=True, dest="aligned", default=None,
|
|
59
60
|
help="use power-of-two aligned buckets (faster, but larger)")
|
|
60
|
-
p.add_argument("--hashfunctions", "--functions",
|
|
61
|
+
p.add_argument("--hashfunctions", "--functions",
|
|
61
62
|
help="hash functions: 'default', 'random', or func1:func2[:func3]")
|
|
62
63
|
# less important hash options
|
|
63
64
|
p.add_argument("--nostatistics", "--nostats", action="store_true",
|
|
64
65
|
help="do not compute or show index statistics at the end")
|
|
65
66
|
p.add_argument("--maxwalk", metavar="INT", type=int, default=500,
|
|
66
67
|
help="maximum length of random walk through hash table before failing [500]")
|
|
67
|
-
p.add_argument("--maxfailures", metavar="INT", type=int, default=0,
|
|
68
|
+
p.add_argument("--maxfailures", metavar="INT", type=int, default=0,
|
|
68
69
|
help="continue even after this many failures [default:0; forever:-1]")
|
|
69
70
|
p.add_argument("--walkseed", type=int, default=7,
|
|
70
71
|
help="seed for random walks while inserting elements [7]")
|
|
@@ -137,7 +137,7 @@ def build_hash(universe, n, subtables, bucketsize,
|
|
|
137
137
|
|
|
138
138
|
@njit(nogil=True, locals=dict(
|
|
139
139
|
bucket=int64, startbit=uint64))
|
|
140
|
-
def
|
|
140
|
+
def prefetch_key(table, key):
|
|
141
141
|
st, sk = get_subtable_subkey_from_key(key)
|
|
142
142
|
bucket, _ = get_bs1(sk)
|
|
143
143
|
startbit = st * subtablebits + bucket * bucketsizebits
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from argparse import ArgumentParser
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
|
-
from numba import njit, int32, int64
|
|
4
|
+
from numba import njit, int32, int64, uint64
|
|
5
5
|
|
|
6
6
|
from .seqio import FormatError, _universal_reads
|
|
7
7
|
from ..lowlevel.libc import read_block
|
|
@@ -14,6 +14,7 @@ debugprint0, debugprint1, debugprint2 = debug.debugprint
|
|
|
14
14
|
|
|
15
15
|
# Python FASTQ/gz handling ######################################
|
|
16
16
|
|
|
17
|
+
|
|
17
18
|
def fastq_reads(files, sequences_only=False, dirty=False):
|
|
18
19
|
"""
|
|
19
20
|
For the given 'files':
|
|
@@ -274,6 +275,253 @@ def cptask_read_pairedfastq_into_linemarked_buffers(fd, inbuffers, incontrol, in
|
|
|
274
275
|
return (wait_read, wait_write, errorcode)
|
|
275
276
|
|
|
276
277
|
|
|
278
|
+
# read fastq without computing linemarks
|
|
279
|
+
|
|
280
|
+
@njit(nogil=True, locals=dict(
|
|
281
|
+
ntodo1=int32, ntodo2=int32, errorcode=int32, offset1=int32, offset2=int32,
|
|
282
|
+
skip=int32, nxt1=int32, nxt2=int32, nseqs=int32, nseqs1=int32, nseqs2=int32, nlines1=int32, nlines2=int32,
|
|
283
|
+
wait=int64, wait_read=int64, wait_write=int64, buffersize=int64))
|
|
284
|
+
def cptask_read_pairedfastq_into_buffers(fd, inbuffers, incontrol, ininfos, outbuffers, outcontrol, outinfos):
|
|
285
|
+
"""
|
|
286
|
+
Has to be run as a thread within a consumer producer task.
|
|
287
|
+
Keep reading bytes into one of the outbuffers (cycling),
|
|
288
|
+
and compute linemarks in one of the outinfos (same index, cycling),
|
|
289
|
+
until EOF is reached or an error occurs.
|
|
290
|
+
Return 0 (EOF), or an error code (negative), corresponding to -os.errno.
|
|
291
|
+
"""
|
|
292
|
+
fd1, fd2 = fd
|
|
293
|
+
M, N = outinfos.shape
|
|
294
|
+
assert N % 4 == 0
|
|
295
|
+
|
|
296
|
+
offset1 = offset2 = ntodo1 = ntodo2 = wait_read = wait_write = 0
|
|
297
|
+
nseqs1 = nseqs2 = 0
|
|
298
|
+
nactive = -1
|
|
299
|
+
active_buffer = outbuffers[0] # irrelevant
|
|
300
|
+
buffersize = active_buffer.shape[0] // 2
|
|
301
|
+
active_buffer1 = active_buffer[:len(active_buffer) // 2]
|
|
302
|
+
active_buffer2 = active_buffer[len(active_buffer) // 2:]
|
|
303
|
+
|
|
304
|
+
while True:
|
|
305
|
+
old = nactive
|
|
306
|
+
old_buffer1 = active_buffer1
|
|
307
|
+
old_buffer2 = active_buffer2
|
|
308
|
+
nactive, wait = find_buffer_for_writing(outcontrol, old)
|
|
309
|
+
if nactive < 0:
|
|
310
|
+
break
|
|
311
|
+
wait_write += wait
|
|
312
|
+
active_buffer = outbuffers[nactive]
|
|
313
|
+
active_buffer1 = active_buffer[:buffersize]
|
|
314
|
+
active_buffer2 = active_buffer[buffersize:buffersize * 2]
|
|
315
|
+
|
|
316
|
+
if old >= 0:
|
|
317
|
+
assert nactive != old
|
|
318
|
+
if offset1 > 0:
|
|
319
|
+
for i in range(offset1):
|
|
320
|
+
active_buffer1[i] = old_buffer1[ntodo1 - offset1 + i]
|
|
321
|
+
if offset2 > 0:
|
|
322
|
+
for i in range(offset2):
|
|
323
|
+
active_buffer2[i] = old_buffer2[ntodo2 - offset2 + i]
|
|
324
|
+
mark_buffer_for_reading(outcontrol, old)
|
|
325
|
+
|
|
326
|
+
errorcode = outcontrol[nactive, 1]
|
|
327
|
+
if errorcode != 0:
|
|
328
|
+
errorcode = -errorcode
|
|
329
|
+
break
|
|
330
|
+
|
|
331
|
+
# only read if the buffer is filled less than 80%
|
|
332
|
+
if offset1 < active_buffer1.size * 4 // 5 or nseqs1 == 0:
|
|
333
|
+
inner_offset1 = offset1
|
|
334
|
+
while inner_offset1 < active_buffer1.size * 4 // 5 or nseqs1 == 0:
|
|
335
|
+
ntodo1 = read_block(int32(fd1), active_buffer1, inner_offset1)
|
|
336
|
+
if ntodo1 == 0:
|
|
337
|
+
ntodo1 = inner_offset1
|
|
338
|
+
break
|
|
339
|
+
if ntodo1 == -99_999:
|
|
340
|
+
debugprint0("- Failed to read file into buffer (probably due to too long reads, len(header+seq+quality) must be smaller than buffersize, default 65kB).")
|
|
341
|
+
debugprint0("- Please increase buffersize using --buffersize.")
|
|
342
|
+
break
|
|
343
|
+
inner_offset1 = ntodo1
|
|
344
|
+
if inner_offset1 >= active_buffer1.size * 4 // 5:
|
|
345
|
+
break
|
|
346
|
+
else:
|
|
347
|
+
ntodo1 = offset1
|
|
348
|
+
|
|
349
|
+
if offset2 < active_buffer2.size * 4 // 5 or nseqs2 == 0:
|
|
350
|
+
inner_offset2 = offset2
|
|
351
|
+
while inner_offset2 < active_buffer2.size * 4 // 5 or nseqs2 == 0:
|
|
352
|
+
ntodo2 = read_block(int32(fd2), active_buffer2, inner_offset2)
|
|
353
|
+
if ntodo2 == 0:
|
|
354
|
+
ntodo2 = inner_offset2
|
|
355
|
+
break
|
|
356
|
+
if ntodo2 == -99_999:
|
|
357
|
+
debugprint0("- Failed to read file into buffer (probably due to too long reads, len(header+seq+quality) must be smaller than buffersize, default 65kB).")
|
|
358
|
+
debugprint0("- Please increase buffersize using --buffersize.")
|
|
359
|
+
break
|
|
360
|
+
inner_offset2 = ntodo2
|
|
361
|
+
if inner_offset2 >= active_buffer2.size * 4 // 5:
|
|
362
|
+
break
|
|
363
|
+
else:
|
|
364
|
+
ntodo2 = offset2
|
|
365
|
+
|
|
366
|
+
if ntodo1 <= 0 or ntodo2 <= 0:
|
|
367
|
+
# Error reading first file
|
|
368
|
+
if ntodo1 < 0:
|
|
369
|
+
errorcode = ntodo1
|
|
370
|
+
break
|
|
371
|
+
# Error reading second file
|
|
372
|
+
if ntodo2 < 0:
|
|
373
|
+
errorcode = ntodo2
|
|
374
|
+
break
|
|
375
|
+
# ntodo1=ntodo2=0 nothing read and no offset left; we are done
|
|
376
|
+
if ntodo1 == ntodo2 == offset1 == offset2 == 0:
|
|
377
|
+
errorcode = ntodo1
|
|
378
|
+
break
|
|
379
|
+
if ntodo1 == 0:
|
|
380
|
+
ntodo1 = offset1
|
|
381
|
+
elif ntodo2 == 0:
|
|
382
|
+
ntodo2 = offset2
|
|
383
|
+
else:
|
|
384
|
+
assert False
|
|
385
|
+
|
|
386
|
+
# count new lines to determine last complete record pair in both buffers
|
|
387
|
+
nlines1, nlines2 = 0, 0
|
|
388
|
+
for i in range(ntodo1):
|
|
389
|
+
nlines1 += active_buffer1[i] == 10
|
|
390
|
+
for i in range(ntodo2):
|
|
391
|
+
nlines2 += active_buffer2[i] == 10
|
|
392
|
+
nseqs1 = nlines1 // 4
|
|
393
|
+
nseqs2 = nlines2 // 4
|
|
394
|
+
nseqs = min(nseqs1, nseqs2)
|
|
395
|
+
|
|
396
|
+
rm_lines = nlines1 - nseqs * 4
|
|
397
|
+
assert rm_lines >= 0
|
|
398
|
+
if rm_lines == 0 and active_buffer1[ntodo1 - 1] == 10:
|
|
399
|
+
offset1 = 0
|
|
400
|
+
else:
|
|
401
|
+
nlines1 = offset1 = 0
|
|
402
|
+
for i in range(ntodo1):
|
|
403
|
+
nlines1 += active_buffer1[ntodo1 - i - 1] == 10
|
|
404
|
+
if nlines1 == rm_lines + 1:
|
|
405
|
+
break
|
|
406
|
+
offset1 += 1
|
|
407
|
+
|
|
408
|
+
rm_lines = nlines2 - nseqs * 4
|
|
409
|
+
assert rm_lines >= 0
|
|
410
|
+
if rm_lines == 0 and active_buffer2[ntodo2 - 1] == 10:
|
|
411
|
+
offset2 = 0
|
|
412
|
+
else:
|
|
413
|
+
nlines2 = offset2 = 0
|
|
414
|
+
for i in range(ntodo2):
|
|
415
|
+
nlines2 += active_buffer2[ntodo2 - i - 1] == 10
|
|
416
|
+
if nlines2 == rm_lines + 1:
|
|
417
|
+
break
|
|
418
|
+
offset2 += 1
|
|
419
|
+
|
|
420
|
+
# update outcontrol
|
|
421
|
+
outcontrol[nactive, 7] = nseqs
|
|
422
|
+
outcontrol[nactive, 4] = ntodo1 - offset1
|
|
423
|
+
outcontrol[nactive, 5] = ntodo2 - offset2
|
|
424
|
+
outcontrol[nactive, 6] = 0
|
|
425
|
+
|
|
426
|
+
mark_buffer_for_writing(outcontrol, nactive, force=True) # nothing left to read; re-use buffer
|
|
427
|
+
return (wait_read, wait_write, errorcode)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
@njit(nogil=True, locals=dict(
|
|
431
|
+
ntodo=int32, errorcode=int32, offset=int32, last_record_end=int32,
|
|
432
|
+
wait=int64, wait_read=int64, wait_write=int64, buffersize=int64, nactive=int32))
|
|
433
|
+
def cptask_read_fastq_into_buffers(fd, inbuffers, incontrol, ininfos, outbuffers, outcontrol, outinfos):
|
|
434
|
+
"""
|
|
435
|
+
Has to be run as a thread within a consumer producer task.
|
|
436
|
+
Keep reading bytes into one of the outbuffers (cycling),
|
|
437
|
+
and compute linemarks in one of the outinfos (same index, cycling),
|
|
438
|
+
until EOF is reached or an error occurs.
|
|
439
|
+
Return 0 (EOF), or an error code (negative), corresponding to -os.errno.
|
|
440
|
+
"""
|
|
441
|
+
M, N = outinfos.shape
|
|
442
|
+
assert N % 4 == 0
|
|
443
|
+
|
|
444
|
+
offset = ntodo = wait_read = wait_write = 0
|
|
445
|
+
nactive = -1
|
|
446
|
+
active_buffer = outbuffers[0] # irrelevant
|
|
447
|
+
line_starts = np.zeros(4, dtype=np.int8)
|
|
448
|
+
line_start_positions = np.zeros(4, np.int32)
|
|
449
|
+
|
|
450
|
+
while True:
|
|
451
|
+
old = nactive
|
|
452
|
+
old_buffer = active_buffer
|
|
453
|
+
nactive, wait = find_buffer_for_writing(outcontrol, old)
|
|
454
|
+
if nactive < 0:
|
|
455
|
+
break
|
|
456
|
+
wait_write += wait
|
|
457
|
+
active_buffer = outbuffers[nactive]
|
|
458
|
+
|
|
459
|
+
if old >= 0:
|
|
460
|
+
assert nactive != old
|
|
461
|
+
if offset > 0:
|
|
462
|
+
for i in range(offset):
|
|
463
|
+
active_buffer[i] = old_buffer[ntodo - offset + i]
|
|
464
|
+
mark_buffer_for_reading(outcontrol, old)
|
|
465
|
+
|
|
466
|
+
errorcode = outcontrol[nactive, 1]
|
|
467
|
+
if errorcode != 0:
|
|
468
|
+
errorcode = -errorcode
|
|
469
|
+
break
|
|
470
|
+
|
|
471
|
+
ntodo = read_block(int32(fd), active_buffer, offset)
|
|
472
|
+
if ntodo <= 0:
|
|
473
|
+
if ntodo < 0 or offset == 0:
|
|
474
|
+
errorcode = ntodo
|
|
475
|
+
if errorcode == -99_999:
|
|
476
|
+
debugprint0("- Failed to read file into buffer (probably due to too long reads, len(header+seq+quality) must be smaller than buffersize, default 65kB).")
|
|
477
|
+
debugprint0("- Please increase buffersize using --buffersize.")
|
|
478
|
+
break
|
|
479
|
+
ntodo = offset
|
|
480
|
+
|
|
481
|
+
# search for last complete entry
|
|
482
|
+
record_end = 0
|
|
483
|
+
if active_buffer[ntodo - 1] == 10:
|
|
484
|
+
record_end = ntodo
|
|
485
|
+
else:
|
|
486
|
+
for i in range(ntodo):
|
|
487
|
+
if active_buffer[ntodo - i - 1] == 10:
|
|
488
|
+
record_end = ntodo - i
|
|
489
|
+
break
|
|
490
|
+
|
|
491
|
+
idx = 0
|
|
492
|
+
for i in range(record_end - 1):
|
|
493
|
+
if active_buffer[record_end - i - 1] == 10:
|
|
494
|
+
line_starts[4 - idx] = active_buffer[record_end - i]
|
|
495
|
+
line_start_positions[4 - idx] = record_end - i
|
|
496
|
+
idx += 1
|
|
497
|
+
if idx == 5:
|
|
498
|
+
break
|
|
499
|
+
|
|
500
|
+
last_record_end = 0
|
|
501
|
+
if idx < 4:
|
|
502
|
+
last_record_end = 0
|
|
503
|
+
elif idx == 4 and line_starts[2] == 43 and active_buffer[0] == 64:
|
|
504
|
+
last_record_end = record_end
|
|
505
|
+
elif idx > 4 and line_starts[0] == 64 and line_starts[2] == 43:
|
|
506
|
+
last_record_end = record_end
|
|
507
|
+
elif idx > 4 and line_starts[1] == 43 and line_starts[3] == 64:
|
|
508
|
+
last_record_end = line_start_positions[3]
|
|
509
|
+
elif idx > 4 and line_starts[0] == 43 and line_starts[2] == 64:
|
|
510
|
+
last_record_end = line_start_positions[2]
|
|
511
|
+
elif idx > 4 and line_starts[1] == 64 and line_starts[3] == 43:
|
|
512
|
+
last_record_end = line_start_positions[1]
|
|
513
|
+
else:
|
|
514
|
+
debugprint0("- Invalid FASTQ file, each record must have four lines.")
|
|
515
|
+
|
|
516
|
+
# add bytes up to last complete record to outcontrol
|
|
517
|
+
offset = ntodo - last_record_end
|
|
518
|
+
outcontrol[nactive, 4] = last_record_end
|
|
519
|
+
outcontrol[nactive, 6] = 0
|
|
520
|
+
|
|
521
|
+
mark_buffer_for_writing(outcontrol, nactive, force=True) # nothing left to read; re-use buffer
|
|
522
|
+
return (wait_read, wait_write, errorcode)
|
|
523
|
+
|
|
524
|
+
|
|
277
525
|
@njit(nogil=True, locals=dict(
|
|
278
526
|
ntodo=int32, errorcode=int32, offset=int32, wait=int64,
|
|
279
527
|
wait_write=int64, sequence_id=int64, nseq=int64))
|
|
@@ -332,7 +580,7 @@ def cptask_read_fastq_seqid_pos_into_buffers(fd, inbuffers, incontrol, ininfos,
|
|
|
332
580
|
|
|
333
581
|
# numba FASTQ handling ######################################
|
|
334
582
|
|
|
335
|
-
@njit(nogil=True)
|
|
583
|
+
@njit(nogil=True, locals=dict(M=int64, i=uint64, m=int64, nxt=int64, line=uint64))
|
|
336
584
|
def _find_fastq_seqmarks(buf, linemarks, sequence_id=-1):
|
|
337
585
|
"""
|
|
338
586
|
Find start and end positions of lines in byte buffer 'buf'.
|
|
@@ -342,7 +590,7 @@ def _find_fastq_seqmarks(buf, linemarks, sequence_id=-1):
|
|
|
342
590
|
|
|
343
591
|
Return pair (m, nxt), where:
|
|
344
592
|
m: number of sequences processed
|
|
345
|
-
nxt: position at which to continue processing
|
|
593
|
+
nxt: position at which to continue processing
|
|
346
594
|
in the next iteration (start of new entry, '@')
|
|
347
595
|
For sequence i, we have
|
|
348
596
|
linemarks[i, 0] = start of sequence line
|
|
@@ -404,7 +652,7 @@ def fastq_chunks(files,
|
|
|
404
652
|
Yield all chunks from a list or tuple of FASTQ 'files'.
|
|
405
653
|
A chunk is a pair (buffer, linemarks), where
|
|
406
654
|
- buffer is readable/writable byte buffer
|
|
407
|
-
- buffer[linemarks[i,0]:linemarks[i,1]] contains the i-th sequence
|
|
655
|
+
- buffer[linemarks[i,0]:linemarks[i,1]] contains the i-th sequence
|
|
408
656
|
of the chunk; other line marks are not stored.
|
|
409
657
|
- The part of linemarks that is returned is such that
|
|
410
658
|
linemarks.shape[0] is at most maxreads and equals the number of reads.
|
|
@@ -452,7 +700,7 @@ def fastq_chunks_paired(pair, bufsize=2**23, maxreads=2**23 // 200):
|
|
|
452
700
|
Yield all chunks from a pair of InputFileHandler objects.
|
|
453
701
|
A chunk is a pair (buffer, linemarks), where
|
|
454
702
|
- buffer is readable/writable byte buffer
|
|
455
|
-
- buffer[linemarks[i,0]:linemarks[i,1]] contains the i-th sequence
|
|
703
|
+
- buffer[linemarks[i,0]:linemarks[i,1]] contains the i-th sequence
|
|
456
704
|
of the chunk; other line marks are not stored.
|
|
457
705
|
- The part of linemarks that is returned is such that
|
|
458
706
|
linemarks.shape[0] is at most maxreads and equals the number of reads.
|
|
@@ -487,7 +735,8 @@ def fastq_chunks_paired(pair, bufsize=2**23, maxreads=2**23 // 200):
|
|
|
487
735
|
available2 = prev2 + read2
|
|
488
736
|
|
|
489
737
|
if read1 == 0 and read2 == 0:
|
|
490
|
-
|
|
738
|
+
if available1 == 0 and available2 == 0:
|
|
739
|
+
break
|
|
491
740
|
|
|
492
741
|
m1, cont1 = _find_fastq_seqmarks(buf1[:available1], linemarks1)
|
|
493
742
|
m2, cont2 = _find_fastq_seqmarks(buf2[:available2], linemarks2)
|
|
@@ -503,6 +752,7 @@ def fastq_chunks_paired(pair, bufsize=2**23, maxreads=2**23 // 200):
|
|
|
503
752
|
else:
|
|
504
753
|
chunk = (buf1, linemarks1[:m2], buf2, linemarks2[:m2])
|
|
505
754
|
cont1 = linemarks1[m2, 2]
|
|
755
|
+
|
|
506
756
|
yield chunk
|
|
507
757
|
prev1 = available1 - cont1
|
|
508
758
|
prev2 = available2 - cont2
|
|
@@ -512,8 +762,8 @@ def fastq_chunks_paired(pair, bufsize=2**23, maxreads=2**23 // 200):
|
|
|
512
762
|
if prev2 > 0:
|
|
513
763
|
buf2[:prev2] = buf2[cont2:available2]
|
|
514
764
|
buf2[prev2:] = 0
|
|
515
|
-
|
|
516
|
-
|
|
765
|
+
assert prev1 < cont1
|
|
766
|
+
assert prev2 < cont2
|
|
517
767
|
|
|
518
768
|
|
|
519
769
|
# FASTQ checking ####################################################
|
|
@@ -564,7 +814,7 @@ def fastqcheck_single(files, args):
|
|
|
564
814
|
|
|
565
815
|
def guess_pairs(filenames, replacer):
|
|
566
816
|
"""guess_pairs(filenames, replacer):
|
|
567
|
-
Return a list of file names where the read number (1/2)
|
|
817
|
+
Return a list of file names where the read number (1/2)
|
|
568
818
|
has been replaced by the 'replacer'.
|
|
569
819
|
Example: guess_pairs(['abc_R1.fq', 'xyz_R1.fq'], 2)
|
|
570
820
|
returns ['abc_R2.fq', 'xyz_R2.fq'].
|
|
@@ -328,6 +328,7 @@ def compile_positional_kmer_processor(shp, func, rcmode="f", new_encoding=False)
|
|
|
328
328
|
|
|
329
329
|
return k, processor
|
|
330
330
|
|
|
331
|
+
|
|
331
332
|
def compile_positional_translated_kmer_processor(shp, func, rcmode="f", new_encoding=False):
|
|
332
333
|
"""
|
|
333
334
|
like compile_positional_kmer_processor, but also translates C->T and G->A and
|
|
@@ -480,9 +481,7 @@ def compile_positional_translated_kmer_processor(shp, func, rcmode="f", new_enco
|
|
|
480
481
|
|
|
481
482
|
def compile_positional_kmer_processor_issh(shp, func, rcmode="f", new_encoding=False):
|
|
482
483
|
"""
|
|
483
|
-
|
|
484
|
-
as an additional argument to func:
|
|
485
|
-
func(hashtable, kmercode, position, *parameters)
|
|
484
|
+
computes all gapped k-mers of a sequences and applies func on them
|
|
486
485
|
"""
|
|
487
486
|
both = (rcmode == "both")
|
|
488
487
|
k = len(shp)
|
|
@@ -594,3 +593,134 @@ def compile_positional_kmer_processor_issh(shp, func, rcmode="f", new_encoding=F
|
|
|
594
593
|
if failed is True:
|
|
595
594
|
return
|
|
596
595
|
return k, issh_processor_fast
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def compile_get_gapped_kmers_issh(mask, rcmode='max', new_encoding=True, sampled=False):
|
|
599
|
+
"""
|
|
600
|
+
computes all gapped k-mers of a sequences and writes them to an numpy array
|
|
601
|
+
|
|
602
|
+
"""
|
|
603
|
+
k = mask.k
|
|
604
|
+
shp = mask.tuple
|
|
605
|
+
w = mask.w
|
|
606
|
+
if k < 1 or k > 32:
|
|
607
|
+
raise ValueError(f"only 1<=k<=32 is supported, but k={k}.")
|
|
608
|
+
revcomp, ccode = compile_revcomp_and_canonical_code_new(k, rcmode) if new_encoding else compile_revcomp_and_canonical_code_old(k, rcmode)
|
|
609
|
+
invalid = uint64(np.iinfo(np.uint64).max)
|
|
610
|
+
popcnt = compile_popcount('uint64')
|
|
611
|
+
cttz = compile_cttz('uint64')
|
|
612
|
+
|
|
613
|
+
weight, width = uint64(len(shp)), uint64(max(shp) + 1)
|
|
614
|
+
sig_mask = 0
|
|
615
|
+
m = np.zeros(width, dtype=np.uint64)
|
|
616
|
+
for i in shp:
|
|
617
|
+
sig_mask |= 1 << (width - i - 1)
|
|
618
|
+
sig_mask = uint64(sig_mask)
|
|
619
|
+
|
|
620
|
+
for i in range(width - 1):
|
|
621
|
+
m[i + 1] = m[i] + (sig_mask >> (width - i - 1) & 1)
|
|
622
|
+
|
|
623
|
+
C = np.zeros((width, width), dtype=np.uint64)
|
|
624
|
+
masks = np.zeros((width, width), dtype=np.uint64)
|
|
625
|
+
for g in range(width):
|
|
626
|
+
for j in range(width - 1):
|
|
627
|
+
for k in shp:
|
|
628
|
+
if k + j < width and (sig_mask >> (width - (k + j) - 1) & 1) == 1 and m[k] == m[k + j] - m[j] + m[g]:
|
|
629
|
+
C[g, j] |= 1 << (width - k - 1)
|
|
630
|
+
masks[g, j] |= (3 << 2 * (weight - m[k + j] - 1))
|
|
631
|
+
|
|
632
|
+
@njit(nogil=True, locals=dict(best=uint64, best_i=uint64, best_j=uint64, use=uint64))
|
|
633
|
+
def find_best_prev(s, to_cover):
|
|
634
|
+
best = 0
|
|
635
|
+
best_i, best_j = 0, 1
|
|
636
|
+
for z in range(0, s - 1):
|
|
637
|
+
for k in range(1, s):
|
|
638
|
+
use = popcnt(to_cover & C[z, k])
|
|
639
|
+
if use > best:
|
|
640
|
+
best = use
|
|
641
|
+
best_i, best_j = z, k
|
|
642
|
+
return best_i, best_j
|
|
643
|
+
|
|
644
|
+
@njit(nogil=True, locals=dict(mask=uint64, n_ones=uint64, positions=uint64, n_ones_new=uint64))
|
|
645
|
+
def compute_shift(g, k):
|
|
646
|
+
mask = masks[g, k]
|
|
647
|
+
n_ones = cttz(mask) // 2
|
|
648
|
+
positions = width - shp[weight - n_ones - 1] - 1 + k
|
|
649
|
+
n_ones_new = popcnt(sig_mask & (2**positions - 1))
|
|
650
|
+
return 2 * (n_ones_new - n_ones)
|
|
651
|
+
|
|
652
|
+
steps = []
|
|
653
|
+
last = 0
|
|
654
|
+
# new position does not need to be covered
|
|
655
|
+
to_cover = sig_mask & ~uint64(1)
|
|
656
|
+
while to_cover != 0:
|
|
657
|
+
g, k = find_best_prev(width - 1, to_cover)
|
|
658
|
+
shift = compute_shift(g, k)
|
|
659
|
+
remove = to_cover & C[g, k]
|
|
660
|
+
to_cover &= ~remove
|
|
661
|
+
steps.append([k, shift, masks[g, k]])
|
|
662
|
+
last = max(last, k)
|
|
663
|
+
steps = np.array(steps, dtype=np.uint64)
|
|
664
|
+
|
|
665
|
+
@njit(nogil=True, locals=dict(code=uint64, shift=uint64, k=uint64, mask=uint64, contained_ns=uint64, c=uint8, end_naive=uint64))
|
|
666
|
+
def issh_processor_fast(ht, seq, start, end, *parameters):
|
|
667
|
+
if (end - start) < (width - 1):
|
|
668
|
+
return
|
|
669
|
+
hashes = np.zeros(last, dtype=np.uint64)
|
|
670
|
+
# compute history
|
|
671
|
+
end_naive = min(start + last, end)
|
|
672
|
+
for i in range(start, end_naive):
|
|
673
|
+
contained_ns = 0
|
|
674
|
+
code = 0
|
|
675
|
+
for j in shp:
|
|
676
|
+
c = seq[i + j]
|
|
677
|
+
contained_ns |= (c > 3)
|
|
678
|
+
code = (code << 2) | min(uint64(c), uint64(3))
|
|
679
|
+
hashes[i] = code
|
|
680
|
+
ht[i] = code if not (contained_ns & sig_mask) else invalid
|
|
681
|
+
|
|
682
|
+
# necessary to check for Ns in first window
|
|
683
|
+
contained_ns = 0
|
|
684
|
+
for i in range(width):
|
|
685
|
+
contained_ns = (contained_ns << 1) | (seq[start + last + i] > 3)
|
|
686
|
+
contained_ns = contained_ns >> 1
|
|
687
|
+
|
|
688
|
+
# compute gapped k-mers iteratively based on previous gapped k-mers
|
|
689
|
+
for i in range(start + last, (end - start) - (width - 1)):
|
|
690
|
+
contained_ns = contained_ns << 1
|
|
691
|
+
code = 0
|
|
692
|
+
for k, shift, mask in steps:
|
|
693
|
+
code |= (hashes[(i - k) % last] & mask) << shift
|
|
694
|
+
c = seq[i + width - 1]
|
|
695
|
+
contained_ns |= c > 3
|
|
696
|
+
code |= min(uint64(c), uint64(3))
|
|
697
|
+
|
|
698
|
+
hashes[i % last] = code
|
|
699
|
+
ht[i] = code if not (contained_ns & sig_mask) else invalid
|
|
700
|
+
|
|
701
|
+
@njit(nogil=True)
|
|
702
|
+
def canonical_codes_inplace(arr, nhashes):
|
|
703
|
+
for i in range(nhashes):
|
|
704
|
+
arr[i] = ccode(arr[i])
|
|
705
|
+
return nhashes
|
|
706
|
+
|
|
707
|
+
@njit(nogil=True)
|
|
708
|
+
def canonical_codes_sampled(arr, nhashes):
|
|
709
|
+
for idx, hash_idx in enumerate(range(0, nhashes, w // 2)):
|
|
710
|
+
arr[idx] = ccode(arr[hash_idx])
|
|
711
|
+
return idx + 1
|
|
712
|
+
|
|
713
|
+
canonical_codes = canonical_codes_sampled if sampled else canonical_codes_inplace
|
|
714
|
+
|
|
715
|
+
@njit(nogil=True)
|
|
716
|
+
def get_gapped_kmers(seq, hashes):
|
|
717
|
+
hashes[:] = 0
|
|
718
|
+
issh_processor_fast(hashes, seq, 0, len(seq))
|
|
719
|
+
return canonical_codes(hashes, len(seq) - w + 1)
|
|
720
|
+
|
|
721
|
+
@njit(nogil=True)
|
|
722
|
+
def get_gapped_kmers_forward(seq, hashes):
|
|
723
|
+
issh_processor_fast(hashes, seq, 0, len(seq))
|
|
724
|
+
return max(0, len(seq) - w + 1)
|
|
725
|
+
|
|
726
|
+
return get_gapped_kmers_forward if rcmode == 'f' and not sampled else get_gapped_kmers
|
|
@@ -87,16 +87,17 @@ def read_block(fd, buf, offset=0, ATTEMPTS=5):
|
|
|
87
87
|
if space <= 0:
|
|
88
88
|
return -99_999 # no space means overflow
|
|
89
89
|
|
|
90
|
+
ptr = buf.ctypes.data
|
|
90
91
|
# Wrap this in a loop to handle EAGAIN errors
|
|
91
92
|
for attempt in range(ATTEMPTS):
|
|
92
|
-
res = _read(fd,
|
|
93
|
-
err = errno()
|
|
93
|
+
res = _read(fd, ptr + offset, space)
|
|
94
94
|
if res > 0:
|
|
95
95
|
# print(res)
|
|
96
96
|
fill = offset + res
|
|
97
97
|
return fill if fill <= buflen else (-99_999) # -99_999 means overflow
|
|
98
98
|
elif res == 0:
|
|
99
99
|
return 0
|
|
100
|
+
err = errno()
|
|
100
101
|
if err == 11:
|
|
101
102
|
continue # EAGAIN: try again, no data
|
|
102
103
|
return (-err) if err > 0 else (-99_998) # negative errno on error
|
|
@@ -109,18 +110,19 @@ def read_block(fd, buf, offset=0, ATTEMPTS=5):
|
|
|
109
110
|
def write_block(fd, buf, n, ATTEMPTS=5):
|
|
110
111
|
"""
|
|
111
112
|
Write n bytes from buf to the file with file descriptor fd (int32).
|
|
112
|
-
Return number of
|
|
113
|
+
Return number of written bytes
|
|
113
114
|
"""
|
|
114
115
|
N = n
|
|
115
116
|
written = 0
|
|
116
117
|
attempts = 0
|
|
118
|
+
ptr = buf.ctypes.data
|
|
117
119
|
while n > 0:
|
|
118
|
-
res = _write(fd,
|
|
119
|
-
err = errno()
|
|
120
|
+
res = _write(fd, ptr + written, n)
|
|
120
121
|
if res > 0:
|
|
121
122
|
written += res
|
|
122
123
|
n -= res
|
|
123
124
|
elif res < 0: # indicates error; error is in err
|
|
125
|
+
err = errno()
|
|
124
126
|
return (-err) if err > 0 else (-99_998)
|
|
125
127
|
else: # res == 0: nothing done
|
|
126
128
|
attempts += 1
|