seqtree 1.2.0__tar.gz → 1.3.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.
- {seqtree-1.2.0 → seqtree-1.3.1}/CHANGELOG.md +18 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/PKG-INFO +1 -1
- {seqtree-1.2.0 → seqtree-1.3.1}/include/seqtree/seqtree.hpp +15 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/pyproject.toml +1 -1
- {seqtree-1.2.0 → seqtree-1.3.1}/python/seqtree/gapblock.py +20 -14
- {seqtree-1.2.0 → seqtree-1.3.1}/src/_bindings.cpp +66 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/index.cpp +128 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/.gitattributes +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/.gitignore +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/CMakeLists.txt +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/LICENSE +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/README.md +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/RELEASE_AUDIT.md +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/include/seqtree/kmer_index.hpp +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/include/seqtree/parallel.hpp +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/include/seqtree/text_index.hpp +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/include/seqtree/types.hpp +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/python/seqtree/__init__.py +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/python/seqtree/control.py +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/python/seqtree/data/control_human_trb_aa.txt.gz +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/python/seqtree/distance.py +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/python/seqtree/evalue.py +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/python/seqtree/layout.py +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/python/seqtree/pairwise.py +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/python/seqtree/pmhc.py +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/python/seqtree/pmhc_evalue.py +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/python/seqtree/py.typed +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/python/seqtree/seeds.py +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/python/seqtree/text_evalue.py +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/alignment_max.cpp +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/atomic_write.hpp +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/blosum45.inc +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/blosum62.inc +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/blosum80.inc +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/codec.cpp +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/count_unit.hpp +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/distance.cpp +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/engine_seqtm.cpp +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/engine_seqtrie.cpp +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/engines.hpp +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/gapblock.cpp +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/kmer_index.cpp +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/pairwise.cpp +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/pairwise_detail.hpp +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/pam100.inc +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/pam250.inc +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/positional_matrix.cpp +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/searcher.cpp +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/structural.inc +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/substitution_matrix.cpp +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/text_index.cpp +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/trie.cpp +0 -0
- {seqtree-1.2.0 → seqtree-1.3.1}/src/trie.hpp +0 -0
|
@@ -3,6 +3,24 @@
|
|
|
3
3
|
All notable changes to `seqtree`. Dates are release dates. From 1.0.0 the project follows semantic
|
|
4
4
|
versioning: breaking changes need a **major** bump.
|
|
5
5
|
|
|
6
|
+
## [1.3.1] — 2026-10-19
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
|
|
10
|
+
- Gap-prior lookup preparation evaluates pure callbacks only for input lengths,
|
|
11
|
+
avoiding cubic callback work for absent lengths while preserving exact scores,
|
|
12
|
+
count reductions and lane-specific priors. Equal-length/empty axes do not
|
|
13
|
+
evaluate unused priors. The dense native lookup layout is unchanged.
|
|
14
|
+
|
|
15
|
+
## [1.3.0] — 2026-10-19
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Bounded complete-query hit consumers for single and coordinated two-index
|
|
20
|
+
searches, with owned numeric buffers, explicit byte budgets and worker cleanup.
|
|
21
|
+
- Optional empty query axes in coordinated search retain original query offsets
|
|
22
|
+
and supported marginal evidence without creating a query for the missing axis.
|
|
23
|
+
|
|
6
24
|
## [1.2.0] — 2026-10-17
|
|
7
25
|
|
|
8
26
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: seqtree
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.1
|
|
4
4
|
Summary: Fast fuzzy search over biological sequences (C++ core, Python bindings)
|
|
5
5
|
Keywords: sequence-search,fuzzy-matching,CDR3,immunology,bioinformatics,trie
|
|
6
6
|
Author-Email: ISALGO laboratory <mikhail.shugay@gmail.com>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
#include "seqtree/types.hpp"
|
|
3
3
|
|
|
4
4
|
#include <cstdint>
|
|
5
|
+
#include <functional>
|
|
5
6
|
#include <memory>
|
|
6
7
|
#include <optional>
|
|
7
8
|
#include <string>
|
|
@@ -13,6 +14,7 @@ namespace seqtree {
|
|
|
13
14
|
|
|
14
15
|
struct Trie; // internal, src/trie.hpp
|
|
15
16
|
struct Scratch; // internal, src/engines.hpp
|
|
17
|
+
struct CountBuffer;
|
|
16
18
|
|
|
17
19
|
// Maps characters to small integer codes (<=32) and back. Case-insensitive on
|
|
18
20
|
// input. Invalid characters encode to kInvalid.
|
|
@@ -134,6 +136,19 @@ public:
|
|
|
134
136
|
std::vector<std::vector<Hit>> search_batch(const std::vector<std::string>& queries,
|
|
135
137
|
const SearchParams&, int threads = 0) const;
|
|
136
138
|
|
|
139
|
+
// One pool, serialized owned primitive blocks; each query is emitted completely.
|
|
140
|
+
// Columns: query index, ref_id, score, n_subs, n_ins, n_dels (all nonnegative).
|
|
141
|
+
void search_batch_consume(const std::vector<std::string>& queries, const SearchParams&,
|
|
142
|
+
const std::function<void(size_t, size_t, CountBuffer&&)>& consume,
|
|
143
|
+
int threads = 0, size_t chunk_size = 32, size_t max_hit_bytes = 64*1024*1024) const;
|
|
144
|
+
|
|
145
|
+
// Coordinate two indexes in one pool; both owned buffers cover the same queries.
|
|
146
|
+
void search_paired_batch_consume(const Index& other,
|
|
147
|
+
const std::vector<std::string>& queries, const std::vector<std::string>& other_queries,
|
|
148
|
+
const SearchParams&,
|
|
149
|
+
const std::function<void(size_t, size_t, CountBuffer&&, CountBuffer&&)>& consume,
|
|
150
|
+
int threads = 0, size_t chunk_size = 32, size_t max_hit_bytes = 64*1024*1024, bool skip_empty = false) const;
|
|
151
|
+
|
|
137
152
|
// Exact edit-count bins 0..max_total_edits; one reusable hit buffer per worker.
|
|
138
153
|
std::vector<std::vector<uint64_t>> edit_histogram_batch(
|
|
139
154
|
const std::vector<std::string>& queries, const SearchParams&,
|
|
@@ -499,19 +499,22 @@ def gapblock_score(
|
|
|
499
499
|
return best + gap_cost(d, gap_open, gap_extend), best_i
|
|
500
500
|
|
|
501
501
|
|
|
502
|
-
def _prior_cube(prior: GapPrior, width: int) -> list[int]:
|
|
502
|
+
def _prior_cube(prior: GapPrior, width: int, lengths=None) -> list[int]:
|
|
503
503
|
"""Flatten ``prior`` to ``[m][d][i]`` with stride ``width + 1``, for the C++ kernel.
|
|
504
504
|
|
|
505
505
|
Only reachable cells are filled: ``d >= 1`` (the prior never applies to equal lengths) and
|
|
506
506
|
``i <= m - d`` (the block start indexes the shorter sequence). The rest stay zero and are
|
|
507
|
-
never read.
|
|
507
|
+
never read. Input lengths restrict callback evaluation to possible full-length pairs;
|
|
508
|
+
the dense native layout is unchanged. None retains the full-width internal oracle.
|
|
508
509
|
"""
|
|
509
510
|
w1 = width + 1
|
|
510
511
|
cube = [0] * (w1 * w1 * w1)
|
|
511
|
-
|
|
512
|
-
|
|
512
|
+
lengths = range(w1) if lengths is None else sorted(set(lengths))
|
|
513
|
+
for row, m in enumerate(lengths):
|
|
514
|
+
for n in reversed(lengths[:row]):
|
|
515
|
+
d = m - n
|
|
513
516
|
base = (m * w1 + d) * w1
|
|
514
|
-
for i in range(
|
|
517
|
+
for i in range(n + 1):
|
|
515
518
|
v = int(prior(i, d, m))
|
|
516
519
|
if v < 0:
|
|
517
520
|
raise ValueError(f"gap_prior({i}, {d}, {m}) = {v} is negative")
|
|
@@ -561,7 +564,8 @@ def score_matrix(
|
|
|
561
564
|
leaving this at 1 with a real matrix makes gaps ~14x cheaper than substitutions.
|
|
562
565
|
gap_extend: Cost per additional gap column.
|
|
563
566
|
gap_prior: :data:`GapPrior`, materialized once into a lookup cube and then read from C++.
|
|
564
|
-
|
|
567
|
+
Evaluated only for unequal input lengths and valid block starts; it must be
|
|
568
|
+
a pure nonnegative function. ``None`` lets the score alone choose the block position.
|
|
565
569
|
alphabet: ``"aa"``, ``"nt"``, or ``"iupac"``. Symbols outside it raise.
|
|
566
570
|
threads: Worker threads; ``0`` means one per core. Rows are disjoint, so this scales.
|
|
567
571
|
|
|
@@ -586,8 +590,9 @@ def score_matrix(
|
|
|
586
590
|
gap_open = _default_gap_open(matrix)
|
|
587
591
|
if gap_open < 0 or gap_extend < 0:
|
|
588
592
|
raise ValueError("gap_open and gap_extend must be >= 0")
|
|
589
|
-
|
|
590
|
-
|
|
593
|
+
lengths = {len(s) for s in q + r}
|
|
594
|
+
width = max(lengths, default=0)
|
|
595
|
+
cube = _prior_cube(gap_prior, width, lengths) if gap_prior is not None else []
|
|
591
596
|
return _gapblock_matrix(q, r, alphabet, matrix, gap_open, gap_extend, cube, width, threads,
|
|
592
597
|
_position_weights(position_weights_by_length, q, r),
|
|
593
598
|
*_group_options(query_group_ids, reference_group_ids, group_distances, q, r),
|
|
@@ -611,8 +616,9 @@ def _batch_options(axes, matrix, gap_open, gap_extend, gap_prior, threads):
|
|
|
611
616
|
go = _batch_integer(_default_gap_open(matrix) if gap_open is None else gap_open, "gap_open")
|
|
612
617
|
ge = _batch_integer(gap_extend, "gap_extend")
|
|
613
618
|
threads = _batch_integer(threads, "threads")
|
|
614
|
-
|
|
615
|
-
|
|
619
|
+
lengths = {len(s) for axis in axes for s in axis}
|
|
620
|
+
width = max(lengths, default=0)
|
|
621
|
+
cube = _prior_cube(gap_prior, width, lengths) if gap_prior is not None else []
|
|
616
622
|
return go, ge, cube, width, threads
|
|
617
623
|
|
|
618
624
|
|
|
@@ -915,11 +921,11 @@ def _additional_geometry(geometry, features):
|
|
|
915
921
|
geometry.additional_features = terms
|
|
916
922
|
|
|
917
923
|
|
|
918
|
-
def _lane_prior(geometry, prior, width):
|
|
924
|
+
def _lane_prior(geometry, prior, width, queries, refs):
|
|
919
925
|
if prior is not None:
|
|
920
926
|
if not hasattr(geometry, "gap_prior"):
|
|
921
927
|
raise RuntimeError("lane priors require native grouped gap_prior support")
|
|
922
|
-
geometry.gap_prior = _prior_cube(prior, width)
|
|
928
|
+
geometry.gap_prior = _prior_cube(prior, width, {len(s) for s in queries + refs})
|
|
923
929
|
|
|
924
930
|
|
|
925
931
|
def _alignment_options(geometry, q, r, mode, scale, left, right, matrix, prior,
|
|
@@ -1099,8 +1105,8 @@ def grouped_paired_sum_count_batch(queries_a, queries_b, refs_a, refs_b, thresho
|
|
|
1099
1105
|
reference_position_weights_beta, feature_offsets_beta, score_quantum_beta)
|
|
1100
1106
|
_additional_geometry(alpha, additional_feature_offsets_alpha)
|
|
1101
1107
|
_additional_geometry(beta, additional_feature_offsets_beta)
|
|
1102
|
-
_lane_prior(alpha, gap_prior_alpha, width)
|
|
1103
|
-
_lane_prior(beta, gap_prior_beta, width)
|
|
1108
|
+
_lane_prior(alpha, gap_prior_alpha, width, qa, ra)
|
|
1109
|
+
_lane_prior(beta, gap_prior_beta, width, qb, rb)
|
|
1104
1110
|
_alignment_options(alpha, qa, ra, alignment_mode, alignment_scale, trim_left, trim_right,
|
|
1105
1111
|
matrix, gap_prior if gap_prior_alpha is None else gap_prior_alpha, position_weights_by_length_alpha, query_position_weights_alpha,
|
|
1106
1112
|
reference_position_weights_alpha)
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
#include <nanobind/stl/vector.h>
|
|
13
13
|
|
|
14
14
|
#include <algorithm>
|
|
15
|
+
#include <exception>
|
|
15
16
|
#include <optional>
|
|
16
17
|
#include <string>
|
|
17
18
|
#include <vector>
|
|
@@ -186,6 +187,53 @@ nb::list py_search_batch(const Index& idx, const std::vector<std::string>& queri
|
|
|
186
187
|
return out;
|
|
187
188
|
}
|
|
188
189
|
|
|
190
|
+
void py_search_batch_consume(const Index& idx, const std::vector<std::string>& queries,
|
|
191
|
+
const PyParams& pp, nb::object consume, int threads, size_t chunk_size, size_t max_hit_bytes) {
|
|
192
|
+
if (!PyCallable_Check(consume.ptr())) throw nb::value_error("consumer must be callable");
|
|
193
|
+
// A callback may mutate the original Python parameters; native workers read
|
|
194
|
+
// only these owned matrix/positional snapshots until the whole pool joins.
|
|
195
|
+
const PyParams owned = pp;
|
|
196
|
+
auto mat = make_matrix(owned, idx.alphabet());
|
|
197
|
+
SearchParams cp = to_cpp(owned, mat ? &*mat : nullptr);
|
|
198
|
+
std::exception_ptr error;
|
|
199
|
+
{
|
|
200
|
+
nb::gil_scoped_release release;
|
|
201
|
+
try {
|
|
202
|
+
idx.search_batch_consume(queries, cp,
|
|
203
|
+
[&](size_t start, size_t stop, CountBuffer&& buffer) {
|
|
204
|
+
nb::gil_scoped_acquire acquire;
|
|
205
|
+
consume(start, stop, nb::cast(std::move(buffer)));
|
|
206
|
+
}, threads, chunk_size, max_hit_bytes);
|
|
207
|
+
} catch (...) { error = std::current_exception(); }
|
|
208
|
+
}
|
|
209
|
+
// Restore the caller's GIL before Python exceptions are restored/destroyed.
|
|
210
|
+
if (error) std::rethrow_exception(error);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
void py_search_paired_batch_consume(const Index& idx, const Index& other,
|
|
214
|
+
const std::vector<std::string>& queries, const std::vector<std::string>& other_queries,
|
|
215
|
+
const PyParams& pp, nb::object consume, int threads, size_t chunk_size, size_t max_hit_bytes, bool skip_empty) {
|
|
216
|
+
if (!PyCallable_Check(consume.ptr())) throw nb::value_error("consumer must be callable");
|
|
217
|
+
// A callback may mutate the original Python parameters; native workers read
|
|
218
|
+
// only these owned matrix/positional snapshots until the whole pool joins.
|
|
219
|
+
const PyParams owned = pp;
|
|
220
|
+
auto mat = make_matrix(owned, idx.alphabet());
|
|
221
|
+
SearchParams cp = to_cpp(owned, mat ? &*mat : nullptr);
|
|
222
|
+
std::exception_ptr error;
|
|
223
|
+
{
|
|
224
|
+
nb::gil_scoped_release release;
|
|
225
|
+
try {
|
|
226
|
+
idx.search_paired_batch_consume(other, queries, other_queries, cp,
|
|
227
|
+
[&](size_t start, size_t stop, CountBuffer&& buffer, CountBuffer&& other_buffer) {
|
|
228
|
+
nb::gil_scoped_acquire acquire;
|
|
229
|
+
consume(start, stop, nb::cast(std::move(buffer)), nb::cast(std::move(other_buffer)));
|
|
230
|
+
}, threads, chunk_size, max_hit_bytes, skip_empty);
|
|
231
|
+
} catch (...) { error = std::current_exception(); }
|
|
232
|
+
}
|
|
233
|
+
// Restore the caller's GIL before Python exceptions are restored/destroyed.
|
|
234
|
+
if (error) std::rethrow_exception(error);
|
|
235
|
+
}
|
|
236
|
+
|
|
189
237
|
std::vector<std::vector<uint64_t>> py_edit_histogram_batch(
|
|
190
238
|
const Index& idx, const std::vector<std::string>& queries,
|
|
191
239
|
const PyParams& pp, int threads, bool exclude_exact, int max_indels) {
|
|
@@ -718,6 +766,24 @@ NB_MODULE(_core, m) {
|
|
|
718
766
|
nb::arg("threads") = 0,
|
|
719
767
|
"Search many queries in parallel (releases the GIL). ``threads=0`` uses all "
|
|
720
768
|
"cores. Returns one hit list per query, in input order.")
|
|
769
|
+
.def("search_batch_consume", &py_search_batch_consume,
|
|
770
|
+
nb::arg("queries"), nb::arg("params"), nb::arg("consume"), nb::arg("threads") = 0,
|
|
771
|
+
nb::arg("chunk_size") = 32, nb::arg("max_hit_bytes") = 64*1024*1024,
|
|
772
|
+
"One population/pool, serialized consume(start, stop, owned_buffer) callbacks. "
|
|
773
|
+
"Read-only uint32 rows: query_index, ref_id, score, n_subs, n_ins, n_dels. "
|
|
774
|
+
"Every query is complete; block order may vary across threads. Output bytes per "
|
|
775
|
+
"block never exceed max_hit_bytes; an oversized query raises, never truncates. "
|
|
776
|
+
"Native worker scratch also holds the current block of hits. Consumer-retained "
|
|
777
|
+
"buffers remain owned; callers control their retention. Parameters are snapshotted.")
|
|
778
|
+
.def("search_paired_batch_consume", &py_search_paired_batch_consume,
|
|
779
|
+
nb::arg("other"), nb::arg("queries"), nb::arg("other_queries"),
|
|
780
|
+
nb::arg("params"), nb::arg("consume"), nb::arg("threads") = 0,
|
|
781
|
+
nb::arg("chunk_size") = 32, nb::arg("max_hit_bytes") = 64*1024*1024, nb::arg("skip_empty") = false,
|
|
782
|
+
"One pool with two Searchers; consume(start, stop, owned_buffer, other_owned_buffer). "
|
|
783
|
+
"Both six-column buffers cover the same complete query block. Combined output "
|
|
784
|
+
"bytes are bounded by max_hit_bytes; block order may vary. Same-alphabet indexes "
|
|
785
|
+
"and equal-length query populations required. Parameters are snapshotted. "
|
|
786
|
+
"skip_empty=True emits no hits for empty query axes; other axes remain searched.")
|
|
721
787
|
.def("edit_histogram_batch", &py_edit_histogram_batch,
|
|
722
788
|
nb::arg("queries"), nb::arg("params"), nb::arg("threads") = 0,
|
|
723
789
|
nb::arg("exclude_exact") = false, nb::arg("max_indels") = -1,
|
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
#include "count_unit.hpp"
|
|
6
6
|
|
|
7
7
|
#include <algorithm>
|
|
8
|
+
#include <atomic>
|
|
8
9
|
#include <cstring>
|
|
9
10
|
#include <fstream>
|
|
11
|
+
#include <iterator>
|
|
10
12
|
#include <limits>
|
|
13
|
+
#include <mutex>
|
|
11
14
|
#include <stdexcept>
|
|
12
15
|
#include <vector>
|
|
13
16
|
|
|
@@ -188,6 +191,131 @@ std::vector<std::vector<Hit>> Index::search_batch(const std::vector<std::string>
|
|
|
188
191
|
return results;
|
|
189
192
|
}
|
|
190
193
|
|
|
194
|
+
void Index::search_batch_consume(const std::vector<std::string>& queries,
|
|
195
|
+
const SearchParams& p,
|
|
196
|
+
const std::function<void(size_t, size_t, CountBuffer&&)>& consume,
|
|
197
|
+
int threads, size_t chunk_size, size_t max_hit_bytes) const {
|
|
198
|
+
constexpr size_t columns = 6;
|
|
199
|
+
const size_t max_rows = max_hit_bytes / (columns * sizeof(uint32_t));
|
|
200
|
+
if (!chunk_size || !max_rows || !consume)
|
|
201
|
+
throw std::invalid_argument("positive chunk_size/max_hit_bytes and a consumer required");
|
|
202
|
+
if (queries.size() > uint64_t(std::numeric_limits<uint32_t>::max()) + 1)
|
|
203
|
+
throw std::overflow_error("query indices exceed the uint32 output buffer");
|
|
204
|
+
// Bound both the primitive output and the per-worker Hit vectors. A single
|
|
205
|
+
// query may still exceed the explicit output cap; detect that without truncation.
|
|
206
|
+
chunk_size = std::min(chunk_size, std::max(size_t(1), max_rows / std::max(size_t(1), size_t(size()))));
|
|
207
|
+
const size_t blocks = queries.size() / chunk_size + (queries.size() % chunk_size != 0);
|
|
208
|
+
std::atomic<bool> cancelled{false};
|
|
209
|
+
std::mutex callback_mutex;
|
|
210
|
+
struct Local { Searcher searcher; std::vector<std::vector<Hit>> hits; };
|
|
211
|
+
parallel_for(blocks, threads,
|
|
212
|
+
[&] { return Local{Searcher(*this), std::vector<std::vector<Hit>>(chunk_size)}; },
|
|
213
|
+
[&](size_t block, Local& worker) {
|
|
214
|
+
if (cancelled.load()) return;
|
|
215
|
+
try {
|
|
216
|
+
const size_t start = block * chunk_size;
|
|
217
|
+
const size_t stop = start + std::min(chunk_size, queries.size() - start);
|
|
218
|
+
size_t rows = 0;
|
|
219
|
+
for (size_t i = start; i < stop; ++i) {
|
|
220
|
+
if (cancelled.load()) return;
|
|
221
|
+
auto& hits = worker.hits[i-start];
|
|
222
|
+
worker.searcher.search_into(queries[i], p, hits);
|
|
223
|
+
if (hits.size() > max_rows - rows)
|
|
224
|
+
throw std::overflow_error("complete query hits exceed max_hit_bytes; increase the explicit buffer limit");
|
|
225
|
+
rows += hits.size();
|
|
226
|
+
}
|
|
227
|
+
CountBuffer buffer;
|
|
228
|
+
buffer.shape = {rows, columns};
|
|
229
|
+
buffer.data.resize(rows * columns);
|
|
230
|
+
size_t offset = 0;
|
|
231
|
+
for (size_t i = start; i < stop; ++i)
|
|
232
|
+
for (const auto& hit : worker.hits[i-start]) {
|
|
233
|
+
const uint32_t values[] = {uint32_t(i), hit.ref_id, uint32_t(hit.score),
|
|
234
|
+
hit.n_subs, hit.n_ins, hit.n_dels};
|
|
235
|
+
std::copy(std::begin(values), std::end(values), buffer.data.begin() + offset);
|
|
236
|
+
offset += columns;
|
|
237
|
+
}
|
|
238
|
+
std::lock_guard<std::mutex> lock(callback_mutex);
|
|
239
|
+
if (!cancelled.load()) {
|
|
240
|
+
try { consume(start, stop, std::move(buffer)); }
|
|
241
|
+
catch (...) { cancelled.store(true); throw; }
|
|
242
|
+
}
|
|
243
|
+
} catch (...) {
|
|
244
|
+
cancelled.store(true);
|
|
245
|
+
throw;
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
void Index::search_paired_batch_consume(const Index& other,
|
|
251
|
+
const std::vector<std::string>& queries, const std::vector<std::string>& other_queries,
|
|
252
|
+
const SearchParams& p,
|
|
253
|
+
const std::function<void(size_t, size_t, CountBuffer&&, CountBuffer&&)>& consume,
|
|
254
|
+
int threads, size_t chunk_size, size_t max_hit_bytes, bool skip_empty) const {
|
|
255
|
+
if (queries.size() != other_queries.size())
|
|
256
|
+
throw std::invalid_argument("paired query populations must have equal lengths");
|
|
257
|
+
if (alphabet() != other.alphabet())
|
|
258
|
+
throw std::invalid_argument("paired indexes must use the same alphabet");
|
|
259
|
+
constexpr size_t columns = 6;
|
|
260
|
+
const size_t max_rows = max_hit_bytes / (columns * sizeof(uint32_t));
|
|
261
|
+
if (!chunk_size || !max_rows || !consume)
|
|
262
|
+
throw std::invalid_argument("positive chunk_size/max_hit_bytes and a consumer required");
|
|
263
|
+
if (queries.size() > uint64_t(std::numeric_limits<uint32_t>::max()) + 1)
|
|
264
|
+
throw std::overflow_error("query indices exceed the uint32 output buffer");
|
|
265
|
+
// Bound both the primitive output and the per-worker Hit vectors. A single
|
|
266
|
+
// query may still exceed the explicit output cap; detect that without truncation.
|
|
267
|
+
chunk_size = std::min(chunk_size, std::max(size_t(1), max_rows / std::max(size_t(1), size_t(size()) + size_t(other.size()))));
|
|
268
|
+
const size_t blocks = queries.size() / chunk_size + (queries.size() % chunk_size != 0);
|
|
269
|
+
std::atomic<bool> cancelled{false};
|
|
270
|
+
std::mutex callback_mutex;
|
|
271
|
+
struct Local { Searcher searcher, other_searcher; std::vector<std::vector<Hit>> hits, other_hits; };
|
|
272
|
+
parallel_for(blocks, threads,
|
|
273
|
+
[&] { return Local{Searcher(*this), Searcher(other), std::vector<std::vector<Hit>>(chunk_size), std::vector<std::vector<Hit>>(chunk_size)}; },
|
|
274
|
+
[&](size_t block, Local& worker) {
|
|
275
|
+
if (cancelled.load()) return;
|
|
276
|
+
try {
|
|
277
|
+
const size_t start = block * chunk_size;
|
|
278
|
+
const size_t stop = start + std::min(chunk_size, queries.size() - start);
|
|
279
|
+
size_t rows = 0;
|
|
280
|
+
for (size_t i = start; i < stop; ++i) {
|
|
281
|
+
if (cancelled.load()) return;
|
|
282
|
+
auto& hits = worker.hits[i-start];
|
|
283
|
+
if (skip_empty && queries[i].empty()) hits.clear();
|
|
284
|
+
else worker.searcher.search_into(queries[i], p, hits);
|
|
285
|
+
auto& other_hits = worker.other_hits[i-start];
|
|
286
|
+
if (skip_empty && other_queries[i].empty()) other_hits.clear();
|
|
287
|
+
else worker.other_searcher.search_into(other_queries[i], p, other_hits);
|
|
288
|
+
if (hits.size() > max_rows - rows || other_hits.size() > max_rows - rows - hits.size())
|
|
289
|
+
throw std::overflow_error("complete query hits exceed max_hit_bytes; increase the explicit buffer limit");
|
|
290
|
+
rows += hits.size() + other_hits.size();
|
|
291
|
+
}
|
|
292
|
+
auto pack = [&](const auto& groups) {
|
|
293
|
+
CountBuffer buffer;
|
|
294
|
+
size_t count = 0;
|
|
295
|
+
for (size_t i = start; i < stop; ++i) count += groups[i-start].size();
|
|
296
|
+
buffer.shape = {count, columns};
|
|
297
|
+
buffer.data.reserve(count * columns);
|
|
298
|
+
for (size_t i = start; i < stop; ++i)
|
|
299
|
+
for (const auto& hit : groups[i-start]) {
|
|
300
|
+
const uint32_t values[] = {uint32_t(i), hit.ref_id, uint32_t(hit.score),
|
|
301
|
+
hit.n_subs, hit.n_ins, hit.n_dels};
|
|
302
|
+
buffer.data.insert(buffer.data.end(), std::begin(values), std::end(values));
|
|
303
|
+
}
|
|
304
|
+
return buffer;
|
|
305
|
+
};
|
|
306
|
+
auto buffer = pack(worker.hits), other_buffer = pack(worker.other_hits);
|
|
307
|
+
std::lock_guard<std::mutex> lock(callback_mutex);
|
|
308
|
+
if (!cancelled.load()) {
|
|
309
|
+
try { consume(start, stop, std::move(buffer), std::move(other_buffer)); }
|
|
310
|
+
catch (...) { cancelled.store(true); throw; }
|
|
311
|
+
}
|
|
312
|
+
} catch (...) {
|
|
313
|
+
cancelled.store(true);
|
|
314
|
+
throw;
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
191
319
|
std::vector<std::vector<uint64_t>> Index::edit_histogram_batch(
|
|
192
320
|
const std::vector<std::string>& queries, const SearchParams& p,
|
|
193
321
|
int threads, bool exclude_exact, int max_indels) const {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|