xengsort 2.0.7__tar.gz → 2.0.9__tar.gz

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