From 226ad94b0f79df39f23e2a27cdfa08151f472480 Mon Sep 17 00:00:00 2001 From: mikessh Date: Sat, 10 Oct 2026 09:45:30 +0300 Subject: [PATCH] Accelerate bounded unit edit histograms Use a word-parallel Levenshtein filter and verify ambiguous survivors under exact insertion/deletion caps. Retain trie traversal for small radii and unsupported geometries. Co-Authored-By: Codex --- src/count_unit.hpp | 87 ++++++++++++++++++++ src/index.cpp | 2 + tests/python/test_unit_histogram_fastpath.py | 49 +++++++++++ 3 files changed, 138 insertions(+) create mode 100644 src/count_unit.hpp create mode 100644 tests/python/test_unit_histogram_fastpath.py diff --git a/src/count_unit.hpp b/src/count_unit.hpp new file mode 100644 index 0000000..3ef3c59 --- /dev/null +++ b/src/count_unit.hpp @@ -0,0 +1,87 @@ +// Private prototype: exact unit histogram acceleration; not installed or integrated. +#pragma once +#include "seqtree/seqtree.hpp" +#include +#include +#include +#include +#include + +namespace seqtree::count_unit { +struct Pattern { + std::array eq{}; + std::array codes{}; + size_t length; + Pattern(std::string_view query, const Codec& codec):length(query.size()) { + if (!length || length>63) throw std::invalid_argument("prototype pattern length must be 1..63"); + for (size_t i=0;iint(r.size()) || s+ins+del>cap) continue; + if (i==int(q.length) && j==int(r.size())) best=std::min(best,s+ins+del); + auto put=[&](int ii,int ni,int nd,int ns) { + if (ns+ni+nd<=cap) dp[ii][ni][nd]=std::min(dp[ii][ni][nd],uint8_t(ns)); + }; + if (iq.length+2 || q.length>r.size()+2) return cap+1; + const int delta=int(r.size())-int(q.length); + const int d=q.distance(r); + if (d>cap) return cap+1; + // For any path, ins+del<=d and |ins-del|=|delta|. Thus max(ins,del) + // <=floor((d+|delta|)/2); both per-type caps are automatic when <=2. + if (d+std::abs(delta)<=5) return d; + return restricted(q,r,codec,cap); +} +// Bounded installed-API profiles favor trie traversal at total caps1/2. +inline bool eligible(const SearchParams& p) { + return !p.matrix && !p.pos_matrix && p.gap_open==1 && + p.max_total_edits>=3 && p.max_total_edits<=5 && + p.max_substitutions>=p.max_total_edits && p.max_insertions==2 && p.max_deletions==2 && + (p.max_score_penalty<=0 || p.max_score_penalty>=p.max_total_edits) && + p.engine!=Engine::SeqTrie && p.mode==Mode::AllHits && !p.max_hits; +} +inline bool fill_histogram(const Index& idx,std::string_view query,const SearchParams& p, + bool exclude_exact,std::vector& out) { + if (!eligible(p) || query.empty() || query.size()>63) return false; + Pattern pattern(query,idx.codec()); + for(uint32_t id=0;id #include @@ -199,6 +200,7 @@ std::vector> Index::edit_histogram_batch( struct Local { Searcher s; std::vector hits; }; parallel_for(queries.size(), threads, [&] { return Local{Searcher(*this), {}}; }, [&](size_t i, Local& worker) { + if (count_unit::fill_histogram(*this,queries[i],p,exclude_exact,out[i])) return; worker.s.search_into(queries[i], p, worker.hits); for (const auto& h : worker.hits) { const uint32_t edits = uint32_t(h.n_subs) + h.n_ins + h.n_dels; diff --git a/tests/python/test_unit_histogram_fastpath.py b/tests/python/test_unit_histogram_fastpath.py new file mode 100644 index 0000000..68bdb19 --- /dev/null +++ b/tests/python/test_unit_histogram_fastpath.py @@ -0,0 +1,49 @@ +"""Exact installed-API oracle for the private unit histogram fast path.""" +import random +import pytest +from seqtree import Index, SearchParams, SubstitutionMatrix, PositionalMatrix + + +def _expected(index, queries, params, exclude): + out=[] + for hits in index.search_batch(queries,params,threads=1): + bins=[0]*(params.max_total_edits+1) + for hit in hits: + total=hit.n_subs+hit.n_ins+hit.n_dels + if not exclude or total:bins[total]+=1 + out.append(bins) + return out + + +def test_exhaustive_binary_and_duplicate_case_counts(): + sequences=['']+[''.join('A' if mask&(1<