xengsort 2.0.5__tar.gz → 2.0.7__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.5 → xengsort-2.0.7}/MANIFEST.in +2 -1
- {xengsort-2.0.5/xengsort.egg-info → xengsort-2.0.7}/PKG-INFO +17 -18
- {xengsort-2.0.5 → xengsort-2.0.7}/README.md +10 -12
- {xengsort-2.0.5 → xengsort-2.0.7}/pyproject.toml +20 -10
- xengsort-2.0.7/tests/test_trimmed_reads.py +28 -0
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/cptasks_kmers.py +305 -8
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/dnaencode.py +11 -8
- xengsort-2.0.7/xengsort/fastcash_info.py +83 -0
- xengsort-2.0.7/xengsort/fastcash_main.py +248 -0
- xengsort-2.0.7/xengsort/fastcash_optimize.py +765 -0
- xengsort-2.0.7/xengsort/hash_minimizer.py +1113 -0
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/hash_s3c_fbcbvb.py +122 -64
- xengsort-2.0.7/xengsort/hash_srh_fbcbvb.py +862 -0
- xengsort-2.0.7/xengsort/io/binaryio.py +62 -0
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/io/fastaio.py +201 -18
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/io/fastqio.py +1 -0
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/io/generalio.py +32 -4
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/io/hashio.py +8 -1
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/io/textio.py +1 -1
- xengsort-2.0.7/xengsort/io/xorio.py +94 -0
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/kmers.py +1 -1
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/lowlevel/bitarray.py +5 -4
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/lowlevel/conpro.py +15 -8
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/lowlevel/intbitarray.py +1 -1
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/lowlevel/llvm.py +1 -1
- xengsort-2.0.7/xengsort/lowlevel/numbautils.py +25 -0
- xengsort-2.0.7/xengsort/lowlevel/packedarray.py +186 -0
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/mathutils.py +5 -3
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/parameters.py +5 -4
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/srhash.py +2 -2
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/subtable_hashfunctions.py +23 -18
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/xengsort/xengsort_classify.py +1 -1
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/xengsort/xengsort_index.py +9 -0
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/xengsort/xengsort_load.py +4 -5
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/xengsort/xengsort_main.py +51 -71
- {xengsort-2.0.5 → xengsort-2.0.7/xengsort.egg-info}/PKG-INFO +17 -18
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort.egg-info/SOURCES.txt +10 -13
- xengsort-2.0.7/xengsort.egg-info/requires.txt +5 -0
- xengsort-2.0.5/tests/test_encoding.py +0 -21
- xengsort-2.0.5/tests/test_trimmed_reads.py +0 -33
- xengsort-2.0.5/xengsort/xengsort/__pycache__/xengsort_classify.cpython-310.pyc +0 -0
- xengsort-2.0.5/xengsort/xengsort/__pycache__/xengsort_classify.cpython-311.pyc +0 -0
- xengsort-2.0.5/xengsort/xengsort/__pycache__/xengsort_index.cpython-310.pyc +0 -0
- xengsort-2.0.5/xengsort/xengsort/__pycache__/xengsort_index.cpython-311.pyc +0 -0
- xengsort-2.0.5/xengsort/xengsort/__pycache__/xengsort_info.cpython-311.pyc +0 -0
- xengsort-2.0.5/xengsort/xengsort/__pycache__/xengsort_main.cpython-310.pyc +0 -0
- xengsort-2.0.5/xengsort/xengsort/__pycache__/xengsort_main.cpython-311.pyc +0 -0
- xengsort-2.0.5/xengsort/xengsort/__pycache__/xengsort_weak.cpython-310.pyc +0 -0
- xengsort-2.0.5/xengsort/xengsort/__pycache__/xengsort_weak.cpython-311.pyc +0 -0
- xengsort-2.0.5/xengsort/xengsort/config/classify.yaml +0 -60
- xengsort-2.0.5/xengsort/xengsort/config/index.yaml +0 -9
- xengsort-2.0.5/xengsort.egg-info/requires.txt +0 -4
- {xengsort-2.0.5 → xengsort-2.0.7}/LICENSE +0 -0
- {xengsort-2.0.5 → xengsort-2.0.7}/setup.cfg +0 -0
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/io/seqio.py +0 -0
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/lowlevel/aligned_arrays.py +0 -0
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/lowlevel/debug.py +0 -0
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/lowlevel/libc.py +0 -0
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/mask.py +0 -0
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/values/xenograft.py +0 -0
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/xengsort/xengsort_info.py +0 -0
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/xengsort/xengsort_remove.py +0 -0
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort/xengsort/xengsort_weak.py +0 -0
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort.egg-info/dependency_links.txt +0 -0
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort.egg-info/entry_points.txt +0 -0
- {xengsort-2.0.5 → xengsort-2.0.7}/xengsort.egg-info/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: xengsort
|
|
3
|
-
Version: 2.0.
|
|
4
|
-
Summary: xenograft sorting of FASTQ reads into five categories: host, graft, both, neither, ambiguous
|
|
3
|
+
Version: 2.0.7
|
|
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
|
|
7
7
|
|
|
@@ -35,13 +35,14 @@ Classifier: Environment :: Console
|
|
|
35
35
|
Classifier: Intended Audience :: Science/Research
|
|
36
36
|
Classifier: Natural Language :: English
|
|
37
37
|
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
38
|
-
Requires-Python:
|
|
38
|
+
Requires-Python: >=3.12
|
|
39
39
|
Description-Content-Type: text/markdown
|
|
40
40
|
License-File: LICENSE
|
|
41
|
-
Requires-Dist: numpy>=
|
|
42
|
-
Requires-Dist: numba>=0.
|
|
41
|
+
Requires-Dist: numpy>=2
|
|
42
|
+
Requires-Dist: numba>=0.60
|
|
43
43
|
Requires-Dist: pytest
|
|
44
|
-
Requires-Dist: jsonargparse
|
|
44
|
+
Requires-Dist: jsonargparse>=4.29.0
|
|
45
|
+
Requires-Dist: pip
|
|
45
46
|
|
|
46
47
|
# xengsort: Fast lightweight accurate xenograft sorting
|
|
47
48
|
|
|
@@ -137,7 +138,7 @@ conda create --name xengsort -c conda-forge -c bioconda --file requirements.txt
|
|
|
137
138
|
```
|
|
138
139
|
which will do the same thing using the packages mentioned in `requirements.txt`.
|
|
139
140
|
|
|
140
|
-
**Note:** While xengsort works on
|
|
141
|
+
**Note:** While xengsort works on Linux without problems, some of the required packages may currently not exist for Windows/Mac.
|
|
141
142
|
|
|
142
143
|
Setting up the environment may take some time, as conda searches and downloads packages.
|
|
143
144
|
After all dependencies are resolved, you activate the environment and install the package from the repository into this environment.
|
|
@@ -148,7 +149,7 @@ pip install -e . # install xengsort package using pip
|
|
|
148
149
|
```
|
|
149
150
|
|
|
150
151
|
|
|
151
|
-
|
|
152
|
+
## Run the Snakemake example workflow
|
|
152
153
|
|
|
153
154
|
You can in principle remove `snakemake-minimal` and `sra-toolkit` from the list of packages.
|
|
154
155
|
However, you will not be able to automatically run the provided example workflow or download sequence datasets from SRA.
|
|
@@ -191,7 +192,7 @@ This will run for quite some time (especially downloads and index creation), bes
|
|
|
191
192
|
The results will appear in the `results/` directory.
|
|
192
193
|
|
|
193
194
|
To run your own human/mouse xenograft analysis, you can continue to use the same index.
|
|
194
|
-
All reference files, including the index
|
|
195
|
+
All reference files, including the index (`.hash` and `.info`) are in the `ref/` directory.
|
|
195
196
|
You can follow the structure of the Snakefile to create your own custom workflow.
|
|
196
197
|
Additional information on how to use `xengsort` is given in the usage guide below.
|
|
197
198
|
|
|
@@ -226,29 +227,27 @@ xengsort index --index myindex -H host.fa.gz -G graft.fa.gz -n 4_500_000_000 [O
|
|
|
226
227
|
|
|
227
228
|
We must specify the size of the hash table:
|
|
228
229
|
|
|
229
|
-
- `-n` or `--nobjects`: number of k-mers that will be stored in the hash table. This depends on the used
|
|
230
|
+
- `-n` or `--nobjects`: number of k-mers that will be stored in the hash table. This depends on the used reference genomes and must be estimated beforehand! As a precise estimate of the number of different k-mers can be difficult, you can err on the safe side and provide a generously large estimate, examine the final (low) load factor and then rebuild the index with a smaller `-n` parameter to achieve the desired load. There are also some tools that quickly estimate the number of distinct k-mers in large files, such as [ntCard](https://github.com/bcgsc/ntCard) or [KmerEstimate](https://github.com/srbehera11/KmerEstimate). As a guide: Human and mouse genome and transcriptome together comprise around 4.5 billion 25-mers, as shown in the examples above.
|
|
230
231
|
**This option must be specified; there is no default!**
|
|
231
232
|
|
|
232
233
|
|
|
233
234
|
We may further specify additional properties of the hash table:
|
|
234
235
|
|
|
235
|
-
- `-
|
|
236
|
+
- `-b` or `--bucketsize` indicates how many elements can be stored in one bucket (or page). This is 4 by default.
|
|
236
237
|
|
|
237
238
|
- `--fill` between 0.0 and 1.0 describes the desired fill rate or load factor of the hash table.
|
|
238
239
|
Together with `-n`, the number of slots in the table is calculated as `ceil(n/fill)`. In our experiments we used 0.88. (The number of buckets is then the smallest odd integer that is at least `ceil(ceil(n/fill)/p)`.)
|
|
239
240
|
|
|
240
|
-
- `--aligned` or `--unaligned`: indicates whether each bucket should consume a number of bits that is a power of 2. Using `--aligned` ensures that each bucket stays within the same cache line, but may waste space (padding bits), yielding faster speed but
|
|
241
|
+
- `--aligned` or `--unaligned`: indicates whether each bucket should consume a number of bits that is a power of 2. Using `--aligned` ensures that each bucket stays within the same cache line, but may waste space (padding bits), yielding faster speed but possibly (much!) larger space requirements. With `--unaligned`, no bits are used for padding and buckets may cross cache line boundaries. This is slightly slower, but may save a little or a lot of space (depending on the bucket size in bits). The default is `--unaligned`, because the speed decrease is small and the memory savings can be significant.
|
|
241
242
|
|
|
242
243
|
- `--hashfunctions` defines the parameters for the hash functions used to store the key-value pairs. If the parameter is unspecified, different random functions are chosen each time. The hash functions can be specified using a colon separated list: `--hashfunctions linear945:linear9123641:linear349341847`. It is recommended to have them chosen randomly unless you need strictly reproducible behavior, in which case the example given here is recommended.
|
|
243
244
|
|
|
244
|
-
The final important parameter is about
|
|
245
|
+
The final important parameter is about parallelization:
|
|
245
246
|
|
|
246
|
-
- `-
|
|
247
|
-
|
|
248
|
-
The following hash table options should not be modified from their default values and are for internal development only:
|
|
249
|
-
|
|
250
|
-
- `-P` or `--parameters` can be used to combine all parameters stated above into one string (see the `--help` text)
|
|
247
|
+
- `-W` or `--weakthreads` defines how many threads are used to calculate weak *k*-mers.
|
|
251
248
|
|
|
249
|
+
Most of the parameters can also be provided in a config file (`.yaml`):
|
|
250
|
+
- `--cfg` or `--config` defines the path the the config file.
|
|
252
251
|
|
|
253
252
|
### How to classify
|
|
254
253
|
|
|
@@ -92,7 +92,7 @@ conda create --name xengsort -c conda-forge -c bioconda --file requirements.txt
|
|
|
92
92
|
```
|
|
93
93
|
which will do the same thing using the packages mentioned in `requirements.txt`.
|
|
94
94
|
|
|
95
|
-
**Note:** While xengsort works on
|
|
95
|
+
**Note:** While xengsort works on Linux without problems, some of the required packages may currently not exist for Windows/Mac.
|
|
96
96
|
|
|
97
97
|
Setting up the environment may take some time, as conda searches and downloads packages.
|
|
98
98
|
After all dependencies are resolved, you activate the environment and install the package from the repository into this environment.
|
|
@@ -103,7 +103,7 @@ pip install -e . # install xengsort package using pip
|
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
## Run the Snakemake example workflow
|
|
107
107
|
|
|
108
108
|
You can in principle remove `snakemake-minimal` and `sra-toolkit` from the list of packages.
|
|
109
109
|
However, you will not be able to automatically run the provided example workflow or download sequence datasets from SRA.
|
|
@@ -146,7 +146,7 @@ This will run for quite some time (especially downloads and index creation), bes
|
|
|
146
146
|
The results will appear in the `results/` directory.
|
|
147
147
|
|
|
148
148
|
To run your own human/mouse xenograft analysis, you can continue to use the same index.
|
|
149
|
-
All reference files, including the index
|
|
149
|
+
All reference files, including the index (`.hash` and `.info`) are in the `ref/` directory.
|
|
150
150
|
You can follow the structure of the Snakefile to create your own custom workflow.
|
|
151
151
|
Additional information on how to use `xengsort` is given in the usage guide below.
|
|
152
152
|
|
|
@@ -181,29 +181,27 @@ xengsort index --index myindex -H host.fa.gz -G graft.fa.gz -n 4_500_000_000 [O
|
|
|
181
181
|
|
|
182
182
|
We must specify the size of the hash table:
|
|
183
183
|
|
|
184
|
-
- `-n` or `--nobjects`: number of k-mers that will be stored in the hash table. This depends on the used
|
|
184
|
+
- `-n` or `--nobjects`: number of k-mers that will be stored in the hash table. This depends on the used reference genomes and must be estimated beforehand! As a precise estimate of the number of different k-mers can be difficult, you can err on the safe side and provide a generously large estimate, examine the final (low) load factor and then rebuild the index with a smaller `-n` parameter to achieve the desired load. There are also some tools that quickly estimate the number of distinct k-mers in large files, such as [ntCard](https://github.com/bcgsc/ntCard) or [KmerEstimate](https://github.com/srbehera11/KmerEstimate). As a guide: Human and mouse genome and transcriptome together comprise around 4.5 billion 25-mers, as shown in the examples above.
|
|
185
185
|
**This option must be specified; there is no default!**
|
|
186
186
|
|
|
187
187
|
|
|
188
188
|
We may further specify additional properties of the hash table:
|
|
189
189
|
|
|
190
|
-
- `-
|
|
190
|
+
- `-b` or `--bucketsize` indicates how many elements can be stored in one bucket (or page). This is 4 by default.
|
|
191
191
|
|
|
192
192
|
- `--fill` between 0.0 and 1.0 describes the desired fill rate or load factor of the hash table.
|
|
193
193
|
Together with `-n`, the number of slots in the table is calculated as `ceil(n/fill)`. In our experiments we used 0.88. (The number of buckets is then the smallest odd integer that is at least `ceil(ceil(n/fill)/p)`.)
|
|
194
194
|
|
|
195
|
-
- `--aligned` or `--unaligned`: indicates whether each bucket should consume a number of bits that is a power of 2. Using `--aligned` ensures that each bucket stays within the same cache line, but may waste space (padding bits), yielding faster speed but
|
|
195
|
+
- `--aligned` or `--unaligned`: indicates whether each bucket should consume a number of bits that is a power of 2. Using `--aligned` ensures that each bucket stays within the same cache line, but may waste space (padding bits), yielding faster speed but possibly (much!) larger space requirements. With `--unaligned`, no bits are used for padding and buckets may cross cache line boundaries. This is slightly slower, but may save a little or a lot of space (depending on the bucket size in bits). The default is `--unaligned`, because the speed decrease is small and the memory savings can be significant.
|
|
196
196
|
|
|
197
197
|
- `--hashfunctions` defines the parameters for the hash functions used to store the key-value pairs. If the parameter is unspecified, different random functions are chosen each time. The hash functions can be specified using a colon separated list: `--hashfunctions linear945:linear9123641:linear349341847`. It is recommended to have them chosen randomly unless you need strictly reproducible behavior, in which case the example given here is recommended.
|
|
198
198
|
|
|
199
|
-
The final important parameter is about
|
|
199
|
+
The final important parameter is about parallelization:
|
|
200
200
|
|
|
201
|
-
- `-
|
|
202
|
-
|
|
203
|
-
The following hash table options should not be modified from their default values and are for internal development only:
|
|
204
|
-
|
|
205
|
-
- `-P` or `--parameters` can be used to combine all parameters stated above into one string (see the `--help` text)
|
|
201
|
+
- `-W` or `--weakthreads` defines how many threads are used to calculate weak *k*-mers.
|
|
206
202
|
|
|
203
|
+
Most of the parameters can also be provided in a config file (`.yaml`):
|
|
204
|
+
- `--cfg` or `--config` defines the path the the config file.
|
|
207
205
|
|
|
208
206
|
### How to classify
|
|
209
207
|
|
|
@@ -2,24 +2,34 @@
|
|
|
2
2
|
requires = ["setuptools>=69.0.3"]
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
[project]
|
|
6
7
|
name = "xengsort"
|
|
7
|
-
version = "2.0.
|
|
8
|
+
version = "2.0.7"
|
|
9
|
+
|
|
8
10
|
license = {file = "LICENSE"}
|
|
11
|
+
|
|
9
12
|
authors = [
|
|
10
13
|
{ name="Jens Zentgraf", email="zentgraf@cs.uni-saarland.de" },
|
|
11
14
|
{ name="Sven Rahmann", email="rahmann@cs.uni-saarland.de" }
|
|
12
15
|
]
|
|
13
|
-
|
|
16
|
+
|
|
17
|
+
description = "xenograft sorting of FASTQ reads into five categories: host, graft, both, neither, ambiguous."
|
|
18
|
+
|
|
14
19
|
readme = "README.md"
|
|
15
|
-
|
|
20
|
+
|
|
21
|
+
requires-python = ">=3.12"
|
|
22
|
+
|
|
16
23
|
dependencies = [
|
|
17
|
-
"numpy>=
|
|
18
|
-
"numba>=0.
|
|
24
|
+
"numpy>=2",
|
|
25
|
+
"numba>=0.60",
|
|
19
26
|
"pytest",
|
|
20
|
-
"jsonargparse",
|
|
27
|
+
"jsonargparse>=4.29.0",
|
|
28
|
+
"pip",
|
|
21
29
|
]
|
|
30
|
+
|
|
22
31
|
scripts = {xengsort = "xengsort.xengsort.xengsort_main:main"}
|
|
32
|
+
|
|
23
33
|
classifiers = [
|
|
24
34
|
"Programming Language :: Python :: 3",
|
|
25
35
|
"License :: OSI Approved :: MIT License",
|
|
@@ -31,9 +41,9 @@ classifiers = [
|
|
|
31
41
|
"Topic :: Scientific/Engineering :: Bio-Informatics"
|
|
32
42
|
]
|
|
33
43
|
|
|
34
|
-
[tool.setuptools]
|
|
35
|
-
packages=["xengsort"]
|
|
36
|
-
|
|
37
44
|
[project.urls]
|
|
38
45
|
"Homepage" = "https://gitlab.com/genomeinformatics/xengsort"
|
|
39
|
-
"Bug Tracker" = "https://gitlab.com/genomeinformatics/xengsort/issues"
|
|
46
|
+
"Bug Tracker" = "https://gitlab.com/genomeinformatics/xengsort/issues"
|
|
47
|
+
|
|
48
|
+
[tool.setuptools]
|
|
49
|
+
packages=["xengsort"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from fastcash.io.fastqio import fastq_chunks_paired
|
|
2
|
+
|
|
3
|
+
def test_fastq_chunks_paired_file2_trimmed():
|
|
4
|
+
pair = ("tests/data/test_chunks1.fq", "tests/data/test_chunks2.fq")
|
|
5
|
+
reads1 = 0
|
|
6
|
+
reads2 = 0
|
|
7
|
+
for buf1, linemarks1, buf2, linemarks2 in fastq_chunks_paired(pair, bufsize=4000, maxreads=40):
|
|
8
|
+
assert len(linemarks1) == len(linemarks2)
|
|
9
|
+
for read in range(len(linemarks1)):
|
|
10
|
+
for nuc in range(linemarks2[read][1]-linemarks2[read][0]-1):
|
|
11
|
+
assert buf2[linemarks2[read][0]:linemarks2[read][1]][nuc] == buf1[linemarks1[read][0]:linemarks1[read][1]][nuc]
|
|
12
|
+
reads1 += len(linemarks1)
|
|
13
|
+
reads2 += len(linemarks2)
|
|
14
|
+
assert reads1 == reads2
|
|
15
|
+
assert reads1 == 60
|
|
16
|
+
|
|
17
|
+
def test_fastq_chunks_paired_file1_trimmed():
|
|
18
|
+
pair = ("tests/data/test_chunks2.fq", "tests/data/test_chunks1.fq")
|
|
19
|
+
reads1 = 0
|
|
20
|
+
reads2 = 0
|
|
21
|
+
for buf1, linemarks1, buf2, linemarks2 in fastq_chunks_paired(pair, bufsize=4000, maxreads=40):
|
|
22
|
+
assert len(linemarks1) == len(linemarks2)
|
|
23
|
+
for read in range(len(linemarks1)):
|
|
24
|
+
for nuc in range(linemarks1[read][1]-linemarks1[read][0]-1):
|
|
25
|
+
assert buf2[linemarks2[read][0]:linemarks2[read][1]][nuc] == buf1[linemarks1[read][0]:linemarks1[read][1]][nuc]
|
|
26
|
+
reads1 += len(linemarks1)
|
|
27
|
+
reads2 += len(linemarks2)
|
|
28
|
+
assert reads1 == reads2 == 60
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
fastcash/cptasks_kmers.py
|
|
3
3
|
Functions for compiling ConsumerProducerTasks for k-mers
|
|
4
4
|
"""
|
|
5
5
|
|
|
@@ -65,6 +65,7 @@ def compile_cptask_scatter_kmers_from_linemarked(
|
|
|
65
65
|
if p >= outbufsize:
|
|
66
66
|
if j >= 0:
|
|
67
67
|
outcontrol[j, 7] = outbufsize
|
|
68
|
+
outcontrol[j, 6] = 1 # buffer type k-mer codes
|
|
68
69
|
mark_buffer_for_reading(outcontrol, j)
|
|
69
70
|
stj = j - st * noutbuffers_per_subtable # the "local" j for subtable st
|
|
70
71
|
assert 0 <= stj < noutbuffers_per_subtable
|
|
@@ -175,6 +176,221 @@ def compile_cptask_scatter_kmers_from_linemarked(
|
|
|
175
176
|
assert outbuffers.shape[1] >= outbufsize
|
|
176
177
|
assert outinfos.shape[1] == 0
|
|
177
178
|
|
|
179
|
+
wait_read = wait_write = 0
|
|
180
|
+
nactive = -1 # active input buffer
|
|
181
|
+
outidx = np.full(nsubtables, (-1), dtype=np.int32)
|
|
182
|
+
outpos = np.full(nsubtables, outbufsize, dtype=np.int32)
|
|
183
|
+
while True:
|
|
184
|
+
nactive, wait = find_buffer_for_reading(incontrol, nactive)
|
|
185
|
+
wait_read += wait
|
|
186
|
+
if nactive < 0: # all finished
|
|
187
|
+
errorcode = int(nactive <= -2)
|
|
188
|
+
break
|
|
189
|
+
active_buffer = inbuffers[nactive]
|
|
190
|
+
buffer_type = incontrol[nactive, 6]
|
|
191
|
+
if buffer_type == 0:
|
|
192
|
+
nseqs = incontrol[nactive, 7]
|
|
193
|
+
errorcode = 0
|
|
194
|
+
linemarks = linemarkbuffers[nactive]
|
|
195
|
+
for i in range(nseqs):
|
|
196
|
+
sq = active_buffer[linemarks[i, 0]:linemarks[i, 1]]
|
|
197
|
+
quick_dna_to_2bits(sq)
|
|
198
|
+
(wait, _errorcode) = _process(
|
|
199
|
+
sq, outidx, outpos, outbuffers, outcontrol)
|
|
200
|
+
wait_write += wait
|
|
201
|
+
errorcode |= _errorcode
|
|
202
|
+
if errorcode:
|
|
203
|
+
mark_my_buffers_failed(incontrol)
|
|
204
|
+
debugprint2("- FAILED: cptask_scatter_kmers", errorcode)
|
|
205
|
+
break
|
|
206
|
+
elif buffer_type == 1:
|
|
207
|
+
nkeys = incontrol[nactive, 7]
|
|
208
|
+
errorcode = 0
|
|
209
|
+
active_buffer = active_buffer.view(np.uint64)
|
|
210
|
+
for i in range(nkeys):
|
|
211
|
+
key = active_buffer[i]
|
|
212
|
+
(wait, _errorcode) = _store(
|
|
213
|
+
key, outidx, outpos, outbuffers, outcontrol)
|
|
214
|
+
wait_write += wait
|
|
215
|
+
errorcode |= _errorcode
|
|
216
|
+
if errorcode:
|
|
217
|
+
mark_my_buffers_failed(incontrol)
|
|
218
|
+
debugprint2("- FAILED: cptask_scatter_kmers", errorcode)
|
|
219
|
+
break
|
|
220
|
+
else:
|
|
221
|
+
raise ValueError("- buffer type {buffer_type} not supported in cptask_scatter_kmers")
|
|
222
|
+
|
|
223
|
+
mark_buffer_for_writing(incontrol, nactive)
|
|
224
|
+
# We break out of while True loop when all bufferes were marked as finished by FASTQ reader processes
|
|
225
|
+
# We then let the next task read our output buffers one final time.
|
|
226
|
+
if not errorcode:
|
|
227
|
+
for st in range(nsubtables):
|
|
228
|
+
j = outidx[st]
|
|
229
|
+
if j >= 0:
|
|
230
|
+
# assert outpos[st] > 0
|
|
231
|
+
outcontrol[j, 7] = outpos[st]
|
|
232
|
+
mark_buffer_for_reading(outcontrol, j)
|
|
233
|
+
debugprint2("- ending: cptask_scatter_kmers", errorcode)
|
|
234
|
+
return (wait_read, wait_write, -errorcode)
|
|
235
|
+
|
|
236
|
+
return _cptask_scatter_kmers
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def compile_cptask_scatter_kmers_position_from_linemarked(
|
|
240
|
+
mask, values, rcmode, hf0,
|
|
241
|
+
nsubtables, noutbuffers_per_subtable, outbufsize):
|
|
242
|
+
"""
|
|
243
|
+
Compile a cptask function that
|
|
244
|
+
creates (gapped) k-mers and value from linemarked buffers.
|
|
245
|
+
The Value is a compressed representation of chromosome and position.
|
|
246
|
+
- mask: a Mask object
|
|
247
|
+
- rcmode: how to treat reverse complements; f, r, b, max, min
|
|
248
|
+
- hf0: the 0-th hash function that determines the subtable
|
|
249
|
+
- nsubtables: the number of subtables
|
|
250
|
+
- noutbuffers_per_subtable: number of output buffers per subtable
|
|
251
|
+
- outbufsize: the output buffer size for which to compile this function
|
|
252
|
+
"""
|
|
253
|
+
debugprint0, debugprint1, debugprint2 = debug.debugprint
|
|
254
|
+
both = (rcmode[0].lower() == "b")
|
|
255
|
+
|
|
256
|
+
k, w, tpl = mask.k, mask.w, mask.tuple
|
|
257
|
+
assert len(tpl) == k
|
|
258
|
+
if k < 1 or k > 32:
|
|
259
|
+
raise ValueError(f"Only 1 <= k <= 32 is supported, but {k=}.")
|
|
260
|
+
codemask = uint64(4**(k - 1) - 1)
|
|
261
|
+
revcomp, ccode = compile_revcomp_and_canonical_code(k, rcmode)
|
|
262
|
+
if mask.is_contiguous:
|
|
263
|
+
debugprint1(f"- compiling cptask_scatter_kmers for contiguous {k}-mers.")
|
|
264
|
+
else:
|
|
265
|
+
debugprint1(f"- compiling cptask_scatter_kmers for gapped ({k},{w})-mers: {mask.mask}.")
|
|
266
|
+
|
|
267
|
+
merge_seq_pos = values.merge_seq_pos
|
|
268
|
+
|
|
269
|
+
@njit(nogil=True, locals=dict(
|
|
270
|
+
code=uint64, st=uint64, sk=uint64,
|
|
271
|
+
j=int32, stj=int32, p=int32, wait=int64))
|
|
272
|
+
def _store(code, chrom, position, outidx, outpos, outbuffers, outcontrol):
|
|
273
|
+
st, sk = hf0(code)
|
|
274
|
+
j = outidx[st]
|
|
275
|
+
p = outpos[st]
|
|
276
|
+
error = 0
|
|
277
|
+
wait_write = 0 # TODO: do we count wait time correctly?
|
|
278
|
+
if p >= outbufsize:
|
|
279
|
+
if j >= 0:
|
|
280
|
+
outcontrol[j, 7] = outbufsize
|
|
281
|
+
outcontrol[j, 6] = 1 # buffer type k-mer codes
|
|
282
|
+
mark_buffer_for_reading(outcontrol, j)
|
|
283
|
+
stj = j - st * noutbuffers_per_subtable # the "local" j for subtable st
|
|
284
|
+
assert 0 <= stj < noutbuffers_per_subtable
|
|
285
|
+
else:
|
|
286
|
+
assert j == -1
|
|
287
|
+
stj = -1
|
|
288
|
+
stoutcontrol = outcontrol[(st * noutbuffers_per_subtable):(st + 1) * noutbuffers_per_subtable]
|
|
289
|
+
stj, wait = find_buffer_for_writing(stoutcontrol, stj)
|
|
290
|
+
outidx[st] = j = (stj + st * noutbuffers_per_subtable)
|
|
291
|
+
outpos[st] = p = 0
|
|
292
|
+
error = outcontrol[j, 1]
|
|
293
|
+
outbuffers[j, p] = sk # put subkey (not code!) into outbuffer
|
|
294
|
+
value = merge_seq_pos(chrom, position)
|
|
295
|
+
outbuffers[j, p + 1] = value
|
|
296
|
+
outpos[st] += 2
|
|
297
|
+
return (wait, error)
|
|
298
|
+
|
|
299
|
+
@njit(nogil=True, locals=dict(
|
|
300
|
+
code=uint64, i=int64, j=int64, c=uint64,
|
|
301
|
+
errorcode=int32, _errorcode=int32,
|
|
302
|
+
wait=int64, _wait=int64))
|
|
303
|
+
def _process_gapped(seq, chrom, offset, *allout): # (outidx, outpos, outbuffers, outcontrol):
|
|
304
|
+
startpoints = seq.size - tpl[k - 1]
|
|
305
|
+
errorcode = 0
|
|
306
|
+
wait = 0
|
|
307
|
+
for i in range(startpoints):
|
|
308
|
+
code = 0
|
|
309
|
+
for j in tpl:
|
|
310
|
+
c = seq[i + j]
|
|
311
|
+
if c > 3:
|
|
312
|
+
break
|
|
313
|
+
code = uint64(code << 2) | uint64(c)
|
|
314
|
+
else: # no break
|
|
315
|
+
if both:
|
|
316
|
+
_wait, errorcode = _store(code, chrom, offset + i, *allout) # outidx, outpos, outbuffers, outcontrol)
|
|
317
|
+
wait += wait
|
|
318
|
+
_wait, _errorcode = _store(revcomp(code), chrom, offset + i, *allout) # outidx, outpos, outbuffers, outcontrol)
|
|
319
|
+
wait += _wait
|
|
320
|
+
errorcode |= _errorcode
|
|
321
|
+
else:
|
|
322
|
+
_wait, errorcode = _store(ccode(code), chrom, offset + i, *allout) # outidx, outpos, outbuffers, outcontrol)
|
|
323
|
+
wait += _wait
|
|
324
|
+
if errorcode != 0:
|
|
325
|
+
break
|
|
326
|
+
return (wait, errorcode)
|
|
327
|
+
|
|
328
|
+
@njit(nogil=True, locals=dict(
|
|
329
|
+
code=uint64, i=int64, j=int64, c=uint64, errorcode=int32,
|
|
330
|
+
wait=int64, _wait=int64))
|
|
331
|
+
def _process_contiguous(seq, chrom, offset, *allout): # (outidx, outpos, outbuffers, outcontrol):
|
|
332
|
+
endpoint = seq.size - (k - 1)
|
|
333
|
+
valid = False
|
|
334
|
+
errorcode = 0
|
|
335
|
+
wait = 0
|
|
336
|
+
i = 0
|
|
337
|
+
while i < endpoint:
|
|
338
|
+
if not valid:
|
|
339
|
+
code = 0
|
|
340
|
+
for j in range(k):
|
|
341
|
+
c = seq[i + j]
|
|
342
|
+
if c > 3:
|
|
343
|
+
i += j + 1 # skip invalid
|
|
344
|
+
break
|
|
345
|
+
code = (code << 2) | c
|
|
346
|
+
else: # no break
|
|
347
|
+
valid = True
|
|
348
|
+
if not valid:
|
|
349
|
+
continue # with while
|
|
350
|
+
else: # was valid, we have an old code
|
|
351
|
+
c = seq[i + k - 1]
|
|
352
|
+
if c > 3:
|
|
353
|
+
valid = False
|
|
354
|
+
i += k # skip invalid
|
|
355
|
+
continue # with while
|
|
356
|
+
code = ((code & codemask) << 2) | c
|
|
357
|
+
# at this point, we have a valid code
|
|
358
|
+
if both:
|
|
359
|
+
_wait, errorcode = _store(code, chrom, offset + i, *allout) # outidx, outpos, outbuffers, outcontrol)
|
|
360
|
+
wait += _wait
|
|
361
|
+
_wait, _errorcode = _store(revcomp(code), chrom, offset + i, *allout) # outidx, outpos, outbuffers, outcontrol)
|
|
362
|
+
wait += _wait
|
|
363
|
+
errorcode |= _errorcode
|
|
364
|
+
else:
|
|
365
|
+
_wait, errorcode = _store(ccode(code), chrom, offset + i, *allout) # outidx, outpos, outbuffers, outcontrol)
|
|
366
|
+
wait += _wait
|
|
367
|
+
i += 1
|
|
368
|
+
if errorcode != 0:
|
|
369
|
+
break
|
|
370
|
+
return (wait, errorcode)
|
|
371
|
+
|
|
372
|
+
_process = _process_contiguous if mask.is_contiguous else _process_gapped
|
|
373
|
+
|
|
374
|
+
@njit(nogil=True, locals=dict(
|
|
375
|
+
nactive=int32, nseqs=int32, i=int32, j=int32, outj=int32,
|
|
376
|
+
wait=int64, wait_read=int64, wait_write=int64,
|
|
377
|
+
errorcode=int32, _errorcode=int32))
|
|
378
|
+
def _cptask_scatter_kmers(inbuffers, incontrol, ininfos, outbuffers, outcontrol, outinfos):
|
|
379
|
+
# find a linemarked buffer to read (among inbuffers)
|
|
380
|
+
# find and reserve nsubtables output buffers among the output buffers [outinfos]
|
|
381
|
+
# incontrol[i, 7]: number of FASTQ reads in input buffer i
|
|
382
|
+
# ininfos[i]: linemarks for input buffer i
|
|
383
|
+
# outcontrol[B, 7]: k-mers in buffer B
|
|
384
|
+
debugprint2("- running: cptask_scatter_kmers; outbuffers shape =", outbuffers.shape)
|
|
385
|
+
assert inbuffers.ndim == 2
|
|
386
|
+
assert outbuffers.ndim == 2
|
|
387
|
+
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
|
|
389
|
+
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
|
|
393
|
+
|
|
178
394
|
wait_read = wait_write = 0
|
|
179
395
|
nactive = -1 # active input buffer
|
|
180
396
|
outidx = np.full(nsubtables, (-1), dtype=np.int32)
|
|
@@ -187,13 +403,18 @@ def compile_cptask_scatter_kmers_from_linemarked(
|
|
|
187
403
|
break
|
|
188
404
|
active_buffer = inbuffers[nactive]
|
|
189
405
|
nseqs = incontrol[nactive, 7]
|
|
406
|
+
buffer_type = incontrol[nactive, 6]
|
|
407
|
+
assert buffer_type == 0 # 0 means sequence buffer
|
|
408
|
+
|
|
190
409
|
errorcode = 0
|
|
191
410
|
linemarks = linemarkbuffers[nactive]
|
|
192
411
|
for i in range(nseqs):
|
|
193
412
|
sq = active_buffer[linemarks[i, 0]:linemarks[i, 1]]
|
|
413
|
+
chromosome = linemarks[i, 4]
|
|
414
|
+
position = linemarks[i, 5]
|
|
194
415
|
quick_dna_to_2bits(sq)
|
|
195
416
|
(wait, _errorcode) = _process(
|
|
196
|
-
sq, outidx, outpos, outbuffers, outcontrol)
|
|
417
|
+
sq, chromosome, position, outidx, outpos, outbuffers, outcontrol)
|
|
197
418
|
wait_write += wait
|
|
198
419
|
errorcode |= _errorcode
|
|
199
420
|
if errorcode:
|
|
@@ -201,7 +422,7 @@ def compile_cptask_scatter_kmers_from_linemarked(
|
|
|
201
422
|
debugprint2("- FAILED: cptask_scatter_kmers", errorcode)
|
|
202
423
|
break
|
|
203
424
|
mark_buffer_for_writing(incontrol, nactive)
|
|
204
|
-
# We break out of while True loop when all
|
|
425
|
+
# We break out of while True loop when all buffers were marked as finished by FASTQ reader processes
|
|
205
426
|
# We then let the next task read our output buffers one final time.
|
|
206
427
|
if not errorcode:
|
|
207
428
|
for st in range(nsubtables):
|
|
@@ -216,6 +437,68 @@ def compile_cptask_scatter_kmers_from_linemarked(
|
|
|
216
437
|
return _cptask_scatter_kmers
|
|
217
438
|
|
|
218
439
|
|
|
440
|
+
def compile_cptask_insert_subkey_value(
|
|
441
|
+
myhashtable,
|
|
442
|
+
maxfailures=0,
|
|
443
|
+
maxwalk=1000):
|
|
444
|
+
debugprint0, debugprint1, debugprint2 = debug.debugprint
|
|
445
|
+
update = myhashtable.private.update_ssk
|
|
446
|
+
|
|
447
|
+
@njit(nogil=True, locals=dict(
|
|
448
|
+
nactive=int32, value=uint64, mytotal=int64, failed=int64,
|
|
449
|
+
nsubkeys=int32, subkey=uint64, status=int32, result=uint64,
|
|
450
|
+
wait=int64, wait_read=int64, wait_write=int64))
|
|
451
|
+
def _cptask_insert_subkey_value(st, ht, ft, inbuffers, incontrol, ininfos, outbuffers, outcontrol, outinfos):
|
|
452
|
+
"""
|
|
453
|
+
Insert subkeys and values of k-mers into subtables of a hash table.
|
|
454
|
+
"""
|
|
455
|
+
# incontrol[i, 0]: buffer status
|
|
456
|
+
# incontrol[i, 1]: set to nonzero on error
|
|
457
|
+
# incontrol[i, 7]: k-mers (or subkeys) in input buffer i
|
|
458
|
+
assert inbuffers.ndim == 2
|
|
459
|
+
assert outbuffers.ndim == 2
|
|
460
|
+
assert outbuffers.shape[0] == 1
|
|
461
|
+
assert outbuffers.shape[1] >= maxwalk + 1
|
|
462
|
+
assert outinfos.shape[1] == 0
|
|
463
|
+
debugprint2("- running: cptask_insert_subkey_value; subtable", st)
|
|
464
|
+
|
|
465
|
+
wait_read = wait_write = 0
|
|
466
|
+
nactive = -1 # active input buffer
|
|
467
|
+
active_buffer = inbuffers[0] # irrelevant
|
|
468
|
+
out = outbuffers[0]
|
|
469
|
+
failed = mytotal = 0
|
|
470
|
+
while True:
|
|
471
|
+
nactive, wait = find_buffer_for_reading(incontrol, nactive)
|
|
472
|
+
wait_read += wait
|
|
473
|
+
if nactive < 0: # all finished
|
|
474
|
+
debugprint2("- ending: cptask_insert_subkey_value; subtable", st, "with", mytotal, "total and", failed, "failed k-mers.")
|
|
475
|
+
break
|
|
476
|
+
active_buffer = inbuffers[nactive]
|
|
477
|
+
nsubkeys = incontrol[nactive, 7]
|
|
478
|
+
buffer_type = incontrol[nactive, 6]
|
|
479
|
+
assert buffer_type == 1 # 1 means k-mer/subkey buffer
|
|
480
|
+
# TODO: NOTE: This is an interleaved subkey/value buffer.
|
|
481
|
+
# Is this a different type?
|
|
482
|
+
|
|
483
|
+
# consume the buffer
|
|
484
|
+
for i in range(0, nsubkeys, 2):
|
|
485
|
+
subkey = active_buffer[i]
|
|
486
|
+
value = active_buffer[i + 1]
|
|
487
|
+
status, result = update(ht, st, subkey, value) # see docstring below
|
|
488
|
+
if status & 128 == 0:
|
|
489
|
+
out[result] += 1
|
|
490
|
+
failed += (status == 0)
|
|
491
|
+
mytotal += nsubkeys
|
|
492
|
+
mark_buffer_for_writing(incontrol, nactive)
|
|
493
|
+
if failed > maxfailures:
|
|
494
|
+
mark_my_buffers_failed(incontrol)
|
|
495
|
+
debugprint2("- FAILED: cptask_insert_subkey_value; subtable", st, "with", mytotal, "total and", failed, "failed k-mers.")
|
|
496
|
+
break
|
|
497
|
+
return (mytotal, failed, wait_read, wait_write, -(failed > 0))
|
|
498
|
+
|
|
499
|
+
return _cptask_insert_subkey_value
|
|
500
|
+
|
|
501
|
+
|
|
219
502
|
def compile_cptask_insert_filtered_subkeys(
|
|
220
503
|
myhashtable,
|
|
221
504
|
myfilter,
|
|
@@ -229,7 +512,7 @@ def compile_cptask_insert_filtered_subkeys(
|
|
|
229
512
|
else:
|
|
230
513
|
wwo = "without"
|
|
231
514
|
|
|
232
|
-
@njit(nogil=True
|
|
515
|
+
@njit(nogil=True)
|
|
233
516
|
def lookup_and_insert_in_subfilter(ft, st, subkey):
|
|
234
517
|
return True
|
|
235
518
|
update = myhashtable.private.update_ssk
|
|
@@ -268,6 +551,9 @@ def compile_cptask_insert_filtered_subkeys(
|
|
|
268
551
|
break
|
|
269
552
|
active_buffer = inbuffers[nactive]
|
|
270
553
|
nsubkeys = incontrol[nactive, 7]
|
|
554
|
+
buffer_type = incontrol[nactive, 6]
|
|
555
|
+
assert buffer_type == 1 # 1 means k-mer buffer (or subkeys)
|
|
556
|
+
|
|
271
557
|
# consume the buffer
|
|
272
558
|
for subkey in active_buffer[:nsubkeys]:
|
|
273
559
|
if lookup_and_insert_in_subfilter(ft, st, subkey):
|
|
@@ -316,11 +602,22 @@ def compile_cptask_update_existing_subkeys(
|
|
|
316
602
|
break
|
|
317
603
|
active_buffer = inbuffers[nactive]
|
|
318
604
|
nsubkeys = incontrol[nactive, 7]
|
|
605
|
+
buffer_type = incontrol[nactive, 6]
|
|
606
|
+
# print("&&& buffer_type", buffer_type)
|
|
607
|
+
assert buffer_type == 1 # 1 means k-mer buffer (0 would be sequence buffer)
|
|
608
|
+
|
|
319
609
|
# consume the buffer
|
|
320
|
-
for
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
610
|
+
for ii in range(nsubkeys):
|
|
611
|
+
subkey = active_buffer[ii]
|
|
612
|
+
_, _ = update_existing(ht, st, subkey, value) # see docstring below
|
|
613
|
+
# status, result = update_existing(ht, st, subkey, value) # see docstring below
|
|
614
|
+
# if status & 128:
|
|
615
|
+
# print("update ->", result)
|
|
616
|
+
# else:
|
|
617
|
+
# print("ignore unknown")
|
|
618
|
+
# if status & 128 == 0:
|
|
619
|
+
# failed += (status == 0) # TODO: should we not just ignore new stuff?
|
|
620
|
+
# The current implementation will count not directly insertable stuff as failures.
|
|
324
621
|
mytotal += nsubkeys
|
|
325
622
|
mark_buffer_for_writing(incontrol, nactive)
|
|
326
623
|
debugprint2("- ending: cptask_update_existing_subkeys; subtable", st, "with", mytotal, "total and", failed, "failed k-mers.")
|