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.
Files changed (55) hide show
  1. {xengsort-2.1.1/xengsort.egg-info → xengsort-2.2.1}/PKG-INFO +1 -1
  2. {xengsort-2.1.1 → xengsort-2.2.1}/pyproject.toml +1 -1
  3. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/cptasks_kmers.py +4 -4
  4. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/fastcash_main.py +5 -4
  5. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/hash_s3c_fbcbvb.py +1 -1
  6. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/io/fastqio.py +259 -9
  7. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/kmers.py +133 -3
  8. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/lowlevel/libc.py +7 -5
  9. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/lowlevel/llvm.py +31 -4
  10. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/srhash.py +1 -1
  11. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/xengsort/config/classify.yaml +1 -1
  12. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/xengsort/xengsort_classify.py +245 -79
  13. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/xengsort/xengsort_index.py +5 -5
  14. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/xengsort/xengsort_main.py +4 -2
  15. {xengsort-2.1.1 → xengsort-2.2.1/xengsort.egg-info}/PKG-INFO +1 -1
  16. {xengsort-2.1.1 → xengsort-2.2.1}/LICENSE +0 -0
  17. {xengsort-2.1.1 → xengsort-2.2.1}/MANIFEST.in +0 -0
  18. {xengsort-2.1.1 → xengsort-2.2.1}/README.md +0 -0
  19. {xengsort-2.1.1 → xengsort-2.2.1}/setup.cfg +0 -0
  20. {xengsort-2.1.1 → xengsort-2.2.1}/tests/test_trimmed_reads.py +0 -0
  21. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/dnaencode.py +0 -0
  22. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/dnaencode_fast.py +0 -0
  23. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/fastcash_info.py +0 -0
  24. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/fastcash_optimize.py +0 -0
  25. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/hash_minimizer.py +0 -0
  26. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/hash_srh_fbcbvb.py +0 -0
  27. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/io/binaryio.py +0 -0
  28. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/io/fastaio.py +0 -0
  29. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/io/generalio.py +0 -0
  30. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/io/hashio.py +0 -0
  31. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/io/seqio.py +0 -0
  32. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/io/textio.py +0 -0
  33. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/io/xorio.py +0 -0
  34. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/lowlevel/aligned_arrays.py +0 -0
  35. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/lowlevel/bitarray.py +0 -0
  36. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/lowlevel/conpro.py +0 -0
  37. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/lowlevel/debug.py +0 -0
  38. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/lowlevel/intbitarray.py +0 -0
  39. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/lowlevel/numbautils.py +0 -0
  40. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/lowlevel/packedarray.py +0 -0
  41. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/mask.py +0 -0
  42. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/mathutils.py +0 -0
  43. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/parameters.py +0 -0
  44. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/subtable_hashfunctions.py +0 -0
  45. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/values/xenograft.py +0 -0
  46. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/xengsort/config/index.yaml +0 -0
  47. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/xengsort/xengsort_info.py +0 -0
  48. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/xengsort/xengsort_load.py +0 -0
  49. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/xengsort/xengsort_remove.py +0 -0
  50. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort/xengsort/xengsort_weak.py +0 -0
  51. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort.egg-info/SOURCES.txt +0 -0
  52. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort.egg-info/dependency_links.txt +0 -0
  53. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort.egg-info/entry_points.txt +0 -0
  54. {xengsort-2.1.1 → xengsort-2.2.1}/xengsort.egg-info/requires.txt +0 -0
  55. {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.1.1
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
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
 
6
6
  [project]
7
7
  name = "xengsort"
8
- version = "2.1.1"
8
+ version = "2.2.1"
9
9
 
10
10
  license = {file = "LICENSE"}
11
11
 
@@ -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
- bisulfite=False):
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 bisulfite:
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 bisulfite:
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 bisulfite:
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 prefetch_for_key(table, key):
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
- break
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
- # assert prev1 < cont1
516
- # assert prev2 < cont2
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
- like compile_kmer_processor, but also uses the current k-mer start position
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, buf.ctypes.data + offset, space)
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 ele
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, buf.ctypes.data, n)
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