sqsketch 0.2.0__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.
- sqsketch-0.2.0/LICENSE +21 -0
- sqsketch-0.2.0/PKG-INFO +204 -0
- sqsketch-0.2.0/README.md +155 -0
- sqsketch-0.2.0/pyproject.toml +39 -0
- sqsketch-0.2.0/setup.cfg +4 -0
- sqsketch-0.2.0/sqsketch/__init__.py +21 -0
- sqsketch-0.2.0/sqsketch/baselines.py +61 -0
- sqsketch-0.2.0/sqsketch/core.py +181 -0
- sqsketch-0.2.0/sqsketch/genomics.py +144 -0
- sqsketch-0.2.0/sqsketch/hashing.py +37 -0
- sqsketch-0.2.0/sqsketch/llm.py +45 -0
- sqsketch-0.2.0/sqsketch.egg-info/PKG-INFO +204 -0
- sqsketch-0.2.0/sqsketch.egg-info/SOURCES.txt +15 -0
- sqsketch-0.2.0/sqsketch.egg-info/dependency_links.txt +1 -0
- sqsketch-0.2.0/sqsketch.egg-info/requires.txt +10 -0
- sqsketch-0.2.0/sqsketch.egg-info/top_level.txt +1 -0
- sqsketch-0.2.0/tests/test_propositions.py +325 -0
sqsketch-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Abderrahmane Sghairi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
sqsketch-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sqsketch
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Fixed-size, abundance-preserving sketches of count profiles over unbounded alphabets
|
|
5
|
+
Author: Abderrahmane Sghairi
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Abderrahmane Sghairi
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/riscoss63/sqsketch
|
|
29
|
+
Project-URL: Paper, https://github.com/riscoss63/sqsketch/tree/main/paper
|
|
30
|
+
Project-URL: DOI, https://doi.org/10.5281/zenodo.22214969
|
|
31
|
+
Keywords: sketching,Bhattacharyya,Hellinger,random projection,hyperdimensional computing,vector symbolic architectures
|
|
32
|
+
Classifier: Development Status :: 3 - Alpha
|
|
33
|
+
Classifier: Intended Audience :: Science/Research
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
37
|
+
Requires-Python: >=3.9
|
|
38
|
+
Description-Content-Type: text/markdown
|
|
39
|
+
License-File: LICENSE
|
|
40
|
+
Requires-Dist: numpy>=1.21
|
|
41
|
+
Requires-Dist: scipy>=1.7
|
|
42
|
+
Provides-Extra: experiments
|
|
43
|
+
Requires-Dist: scikit-learn>=1.0; extra == "experiments"
|
|
44
|
+
Requires-Dist: torch>=2.0; extra == "experiments"
|
|
45
|
+
Requires-Dist: transformers>=4.30; extra == "experiments"
|
|
46
|
+
Provides-Extra: dev
|
|
47
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
48
|
+
Dynamic: license-file
|
|
49
|
+
|
|
50
|
+
# sqsketch
|
|
51
|
+
|
|
52
|
+
[](https://doi.org/10.5281/zenodo.22214969)
|
|
53
|
+
[](https://github.com/riscoss63/sqsketch/actions/workflows/ci.yml)
|
|
54
|
+
[](LICENSE)
|
|
55
|
+
|
|
56
|
+
**Compare two probability or count profiles from a fixed number of bytes, however large the
|
|
57
|
+
alphabet.**
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from sqsketch import Sketch
|
|
61
|
+
|
|
62
|
+
a = Sketch.from_dict({"apple": 12, "pear": 3, "quince": 1})
|
|
63
|
+
b = Sketch.from_dense(probability_vector, D=1024)
|
|
64
|
+
|
|
65
|
+
a.similarity(b) # Bhattacharyya coefficient
|
|
66
|
+
a.hellinger(b) # Hellinger distance
|
|
67
|
+
a.confidence_interval(b) # computed from the two sketches alone, without the profiles
|
|
68
|
+
a.kl_lower_bound(b) # certified: the KL divergence is at least this
|
|
69
|
+
a.merge(b) # a sketch of the pooled profile
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Each sketch is `D` numbers. No vocabulary, no codebook, no inverted index; encoding is one
|
|
73
|
+
pass over the items. The accuracy depends on `D` alone — the number of *possible* items never
|
|
74
|
+
enters the error, so the same width serves an alphabet of a thousand or of 4³¹.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install -e . # numpy and scipy, nothing else
|
|
78
|
+
pytest # 26 tests, one per claim in the paper, ~18 s
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Should you use it? One number decides
|
|
82
|
+
|
|
83
|
+
The thing you are already doing — keeping the `k` heaviest items and lumping the rest into
|
|
84
|
+
one bucket — is the competitor. Top-`k` logprobs, frequent-item tables, truncated term
|
|
85
|
+
vectors are all this. What truncation cannot represent is the mass it throws away, so
|
|
86
|
+
measure that:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from sqsketch.baselines import tail_mass
|
|
90
|
+
tail_mass(your_profiles, k) # mass outside the top k, at your byte budget
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
| tail mass at your budget | verdict |
|
|
94
|
+
|--------------------------|---------|
|
|
95
|
+
| below ≈ 0.10 | keep the top `k` — simpler, and more accurate |
|
|
96
|
+
| 0.15 – 0.4 | sketch wins, by 1.4× to 4× |
|
|
97
|
+
| above 0.7 | sketch wins, by 4× to 7× |
|
|
98
|
+
|
|
99
|
+
The *effective support* `1/Σp²` is **not** the predictor: across the sweep that produced this
|
|
100
|
+
table it ranged from 4 to 800 000 without changing the verdict. Measured on real data, the
|
|
101
|
+
criterion called 14 of 14 cases correctly:
|
|
102
|
+
|
|
103
|
+
| domain | alphabet | tail mass | outcome |
|
|
104
|
+
|--------|---------:|----------:|---------|
|
|
105
|
+
| 21-mer abundance profiles (10 NCBI genomes) | 1 432 940 | 0.992 | sketch, 6.7× |
|
|
106
|
+
| USDT transfer counts per address (live chain) | 27 049 | 0.589 | sketch, 10× |
|
|
107
|
+
| personalised PageRank, 200 000-node graph | 200 000 | 0.502 | sketch, 2.1× |
|
|
108
|
+
| GPT-2 output aggregated over a corpus | 50 257 | 0.412 | sketch, 6.3× |
|
|
109
|
+
| GPT-2 next token, one position | 50 257 | 0.105 | truncation |
|
|
110
|
+
| USDT transfer *value* per address | 27 049 | 0.037 | truncation |
|
|
111
|
+
| document term counts | 45 969 | 0.039 | tie |
|
|
112
|
+
| binned returns, trade sizes (Binance) | 400 / 300 | 0.000 | truncation, exactly |
|
|
113
|
+
|
|
114
|
+
Value-weighted flows are dominated by a handful of addresses; activity counts are spread
|
|
115
|
+
over tens of thousands. Same data, opposite verdicts — which is why the criterion is worth
|
|
116
|
+
measuring rather than guessing.
|
|
117
|
+
|
|
118
|
+
## What it will not do
|
|
119
|
+
|
|
120
|
+
- **Exact top-1 retrieval among near-identical neighbours.** Accuracy is governed by the gap
|
|
121
|
+
between the true nearest neighbour and the runner-up, against the noise floor `√(2/D)`.
|
|
122
|
+
On a real text corpus that gap is ~0.03 and recall@1 falls apart; recall@10 stays at 97 %.
|
|
123
|
+
Use `Index.search` as a candidate generator and rerank the shortlist exactly.
|
|
124
|
+
- **Sampling-noise-dominated histograms.** If each profile is a small sample from a much
|
|
125
|
+
larger alphabet, Hellinger between two empirical histograms mostly measures sample
|
|
126
|
+
overlap. That is a property of the statistic, not of the sketch, but it rules the approach
|
|
127
|
+
out there.
|
|
128
|
+
- **Upper-bounding the KL divergence.** `kl_lower_bound` is one-sided by construction: it
|
|
129
|
+
certifies that two profiles are far apart, never that they are close.
|
|
130
|
+
- **Forecasting anything.** It measures a distance between two distributions. It has no
|
|
131
|
+
notion of time, and confers no predictive edge.
|
|
132
|
+
|
|
133
|
+
## Accuracy
|
|
134
|
+
|
|
135
|
+
Unbiased at every width, with variance `σ²/D` where `σ² = 1 + BC² − 2⟨Q,P⟩ < 2` for every
|
|
136
|
+
pair and every alphabet size, so the standard error is at most `√(2/D)`:
|
|
137
|
+
|
|
138
|
+
| `D` | bytes (float32) | standard error at most |
|
|
139
|
+
|-----|-----------------|------------------------|
|
|
140
|
+
| 256 | 1 KB | 0.088 |
|
|
141
|
+
| 1024 | 4 KB | 0.044 |
|
|
142
|
+
| 4096 | 16 KB | 0.022 |
|
|
143
|
+
|
|
144
|
+
`confidence_interval` is asymptotic in `D` and under-covers below `D ≈ 256`; above that it is
|
|
145
|
+
valid and deliberately conservative, since its width is calibrated for the raw inner product
|
|
146
|
+
while `similarity` returns the lower-variance self-normalised cosine.
|
|
147
|
+
|
|
148
|
+
## How to audit this
|
|
149
|
+
|
|
150
|
+
Every claim is checked twice: as a unit test, and as an end-to-end reproduction.
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
pytest # 26 tests, one per proposition
|
|
154
|
+
cd experiments
|
|
155
|
+
python reproduce.py > outputs/reproduce_output.txt # 14 sections
|
|
156
|
+
python survey.py > outputs/survey_output.txt # the decision criterion
|
|
157
|
+
python verify.py # 36 checks, 5 batteries
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`verify.py` is the part worth knowing about. Beyond checking the mathematics, **battery 3
|
|
161
|
+
extracts every experimental number printed in the paper and requires it to appear in a
|
|
162
|
+
script's output.** The manuscript this work supersedes reported a correlation from one
|
|
163
|
+
column of a table as though it came from another; that class of error is invisible to
|
|
164
|
+
proofreading, so it is checked mechanically. It currently matches 142 of 142.
|
|
165
|
+
|
|
166
|
+
## Repository
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
sqsketch/ the library: core.py, hashing.py, baselines.py
|
|
170
|
+
adapters: genomics.py (k-mers, FASTA, MinHash baselines), llm.py
|
|
171
|
+
tests/ one test per proposition
|
|
172
|
+
paper/ square_root_sketch.tex, and the superseded v1 draft it retracts
|
|
173
|
+
experiments/ everything that produces a number in the paper, plus verify.py
|
|
174
|
+
data/ reference genomes, downloaded on demand (not in git)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## The paper
|
|
178
|
+
|
|
179
|
+
*Norm-Invariance in Vector-Symbolic Encodings of Probability Distributions* — why the square
|
|
180
|
+
root is the only exponent that makes the error independent of the alphabet size, what the
|
|
181
|
+
vector's magnitude therefore cannot encode, and how to read one bit.
|
|
182
|
+
|
|
183
|
+
It carries three explicit retractions of earlier claims, an eight-item limitations section,
|
|
184
|
+
and 20 references each checked against the publisher record. The variance formula it uses is
|
|
185
|
+
**not new** and is attributed throughout to Li, Hastie and Church (2006).
|
|
186
|
+
|
|
187
|
+
## Citing
|
|
188
|
+
|
|
189
|
+
```bibtex
|
|
190
|
+
@software{sghairi2026sqsketch,
|
|
191
|
+
author = {Sghairi, Abderrahmane},
|
|
192
|
+
title = {sqsketch: alphabet-independent sketches of discrete
|
|
193
|
+
probability and count profiles},
|
|
194
|
+
year = {2026},
|
|
195
|
+
version = {0.2.0},
|
|
196
|
+
doi = {10.5281/zenodo.22214969},
|
|
197
|
+
url = {https://github.com/riscoss63/sqsketch}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Licence
|
|
202
|
+
|
|
203
|
+
MIT for the code. The Zenodo record is deposited under the same terms; note that the
|
|
204
|
+
manuscript in `paper/` is part of the same deposit.
|
sqsketch-0.2.0/README.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# sqsketch
|
|
2
|
+
|
|
3
|
+
[](https://doi.org/10.5281/zenodo.22214969)
|
|
4
|
+
[](https://github.com/riscoss63/sqsketch/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
**Compare two probability or count profiles from a fixed number of bytes, however large the
|
|
8
|
+
alphabet.**
|
|
9
|
+
|
|
10
|
+
```python
|
|
11
|
+
from sqsketch import Sketch
|
|
12
|
+
|
|
13
|
+
a = Sketch.from_dict({"apple": 12, "pear": 3, "quince": 1})
|
|
14
|
+
b = Sketch.from_dense(probability_vector, D=1024)
|
|
15
|
+
|
|
16
|
+
a.similarity(b) # Bhattacharyya coefficient
|
|
17
|
+
a.hellinger(b) # Hellinger distance
|
|
18
|
+
a.confidence_interval(b) # computed from the two sketches alone, without the profiles
|
|
19
|
+
a.kl_lower_bound(b) # certified: the KL divergence is at least this
|
|
20
|
+
a.merge(b) # a sketch of the pooled profile
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Each sketch is `D` numbers. No vocabulary, no codebook, no inverted index; encoding is one
|
|
24
|
+
pass over the items. The accuracy depends on `D` alone — the number of *possible* items never
|
|
25
|
+
enters the error, so the same width serves an alphabet of a thousand or of 4³¹.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install -e . # numpy and scipy, nothing else
|
|
29
|
+
pytest # 26 tests, one per claim in the paper, ~18 s
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Should you use it? One number decides
|
|
33
|
+
|
|
34
|
+
The thing you are already doing — keeping the `k` heaviest items and lumping the rest into
|
|
35
|
+
one bucket — is the competitor. Top-`k` logprobs, frequent-item tables, truncated term
|
|
36
|
+
vectors are all this. What truncation cannot represent is the mass it throws away, so
|
|
37
|
+
measure that:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from sqsketch.baselines import tail_mass
|
|
41
|
+
tail_mass(your_profiles, k) # mass outside the top k, at your byte budget
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
| tail mass at your budget | verdict |
|
|
45
|
+
|--------------------------|---------|
|
|
46
|
+
| below ≈ 0.10 | keep the top `k` — simpler, and more accurate |
|
|
47
|
+
| 0.15 – 0.4 | sketch wins, by 1.4× to 4× |
|
|
48
|
+
| above 0.7 | sketch wins, by 4× to 7× |
|
|
49
|
+
|
|
50
|
+
The *effective support* `1/Σp²` is **not** the predictor: across the sweep that produced this
|
|
51
|
+
table it ranged from 4 to 800 000 without changing the verdict. Measured on real data, the
|
|
52
|
+
criterion called 14 of 14 cases correctly:
|
|
53
|
+
|
|
54
|
+
| domain | alphabet | tail mass | outcome |
|
|
55
|
+
|--------|---------:|----------:|---------|
|
|
56
|
+
| 21-mer abundance profiles (10 NCBI genomes) | 1 432 940 | 0.992 | sketch, 6.7× |
|
|
57
|
+
| USDT transfer counts per address (live chain) | 27 049 | 0.589 | sketch, 10× |
|
|
58
|
+
| personalised PageRank, 200 000-node graph | 200 000 | 0.502 | sketch, 2.1× |
|
|
59
|
+
| GPT-2 output aggregated over a corpus | 50 257 | 0.412 | sketch, 6.3× |
|
|
60
|
+
| GPT-2 next token, one position | 50 257 | 0.105 | truncation |
|
|
61
|
+
| USDT transfer *value* per address | 27 049 | 0.037 | truncation |
|
|
62
|
+
| document term counts | 45 969 | 0.039 | tie |
|
|
63
|
+
| binned returns, trade sizes (Binance) | 400 / 300 | 0.000 | truncation, exactly |
|
|
64
|
+
|
|
65
|
+
Value-weighted flows are dominated by a handful of addresses; activity counts are spread
|
|
66
|
+
over tens of thousands. Same data, opposite verdicts — which is why the criterion is worth
|
|
67
|
+
measuring rather than guessing.
|
|
68
|
+
|
|
69
|
+
## What it will not do
|
|
70
|
+
|
|
71
|
+
- **Exact top-1 retrieval among near-identical neighbours.** Accuracy is governed by the gap
|
|
72
|
+
between the true nearest neighbour and the runner-up, against the noise floor `√(2/D)`.
|
|
73
|
+
On a real text corpus that gap is ~0.03 and recall@1 falls apart; recall@10 stays at 97 %.
|
|
74
|
+
Use `Index.search` as a candidate generator and rerank the shortlist exactly.
|
|
75
|
+
- **Sampling-noise-dominated histograms.** If each profile is a small sample from a much
|
|
76
|
+
larger alphabet, Hellinger between two empirical histograms mostly measures sample
|
|
77
|
+
overlap. That is a property of the statistic, not of the sketch, but it rules the approach
|
|
78
|
+
out there.
|
|
79
|
+
- **Upper-bounding the KL divergence.** `kl_lower_bound` is one-sided by construction: it
|
|
80
|
+
certifies that two profiles are far apart, never that they are close.
|
|
81
|
+
- **Forecasting anything.** It measures a distance between two distributions. It has no
|
|
82
|
+
notion of time, and confers no predictive edge.
|
|
83
|
+
|
|
84
|
+
## Accuracy
|
|
85
|
+
|
|
86
|
+
Unbiased at every width, with variance `σ²/D` where `σ² = 1 + BC² − 2⟨Q,P⟩ < 2` for every
|
|
87
|
+
pair and every alphabet size, so the standard error is at most `√(2/D)`:
|
|
88
|
+
|
|
89
|
+
| `D` | bytes (float32) | standard error at most |
|
|
90
|
+
|-----|-----------------|------------------------|
|
|
91
|
+
| 256 | 1 KB | 0.088 |
|
|
92
|
+
| 1024 | 4 KB | 0.044 |
|
|
93
|
+
| 4096 | 16 KB | 0.022 |
|
|
94
|
+
|
|
95
|
+
`confidence_interval` is asymptotic in `D` and under-covers below `D ≈ 256`; above that it is
|
|
96
|
+
valid and deliberately conservative, since its width is calibrated for the raw inner product
|
|
97
|
+
while `similarity` returns the lower-variance self-normalised cosine.
|
|
98
|
+
|
|
99
|
+
## How to audit this
|
|
100
|
+
|
|
101
|
+
Every claim is checked twice: as a unit test, and as an end-to-end reproduction.
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
pytest # 26 tests, one per proposition
|
|
105
|
+
cd experiments
|
|
106
|
+
python reproduce.py > outputs/reproduce_output.txt # 14 sections
|
|
107
|
+
python survey.py > outputs/survey_output.txt # the decision criterion
|
|
108
|
+
python verify.py # 36 checks, 5 batteries
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`verify.py` is the part worth knowing about. Beyond checking the mathematics, **battery 3
|
|
112
|
+
extracts every experimental number printed in the paper and requires it to appear in a
|
|
113
|
+
script's output.** The manuscript this work supersedes reported a correlation from one
|
|
114
|
+
column of a table as though it came from another; that class of error is invisible to
|
|
115
|
+
proofreading, so it is checked mechanically. It currently matches 142 of 142.
|
|
116
|
+
|
|
117
|
+
## Repository
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
sqsketch/ the library: core.py, hashing.py, baselines.py
|
|
121
|
+
adapters: genomics.py (k-mers, FASTA, MinHash baselines), llm.py
|
|
122
|
+
tests/ one test per proposition
|
|
123
|
+
paper/ square_root_sketch.tex, and the superseded v1 draft it retracts
|
|
124
|
+
experiments/ everything that produces a number in the paper, plus verify.py
|
|
125
|
+
data/ reference genomes, downloaded on demand (not in git)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## The paper
|
|
129
|
+
|
|
130
|
+
*Norm-Invariance in Vector-Symbolic Encodings of Probability Distributions* — why the square
|
|
131
|
+
root is the only exponent that makes the error independent of the alphabet size, what the
|
|
132
|
+
vector's magnitude therefore cannot encode, and how to read one bit.
|
|
133
|
+
|
|
134
|
+
It carries three explicit retractions of earlier claims, an eight-item limitations section,
|
|
135
|
+
and 20 references each checked against the publisher record. The variance formula it uses is
|
|
136
|
+
**not new** and is attributed throughout to Li, Hastie and Church (2006).
|
|
137
|
+
|
|
138
|
+
## Citing
|
|
139
|
+
|
|
140
|
+
```bibtex
|
|
141
|
+
@software{sghairi2026sqsketch,
|
|
142
|
+
author = {Sghairi, Abderrahmane},
|
|
143
|
+
title = {sqsketch: alphabet-independent sketches of discrete
|
|
144
|
+
probability and count profiles},
|
|
145
|
+
year = {2026},
|
|
146
|
+
version = {0.2.0},
|
|
147
|
+
doi = {10.5281/zenodo.22214969},
|
|
148
|
+
url = {https://github.com/riscoss63/sqsketch}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Licence
|
|
153
|
+
|
|
154
|
+
MIT for the code. The Zenodo record is deposited under the same terms; note that the
|
|
155
|
+
manuscript in `paper/` is part of the same deposit.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sqsketch"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Fixed-size, abundance-preserving sketches of count profiles over unbounded alphabets"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
authors = [{ name = "Abderrahmane Sghairi" }]
|
|
13
|
+
keywords = ["sketching", "Bhattacharyya", "Hellinger", "random projection",
|
|
14
|
+
"hyperdimensional computing", "vector symbolic architectures"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Science/Research",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Topic :: Scientific/Engineering :: Mathematics",
|
|
21
|
+
]
|
|
22
|
+
dependencies = ["numpy>=1.21", "scipy>=1.7"]
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
# Only the experiments need these. The library itself needs numpy and scipy.
|
|
26
|
+
experiments = ["scikit-learn>=1.0", "torch>=2.0", "transformers>=4.30"]
|
|
27
|
+
dev = ["pytest>=7.0"]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/riscoss63/sqsketch"
|
|
31
|
+
Paper = "https://github.com/riscoss63/sqsketch/tree/main/paper"
|
|
32
|
+
DOI = "https://doi.org/10.5281/zenodo.22214969"
|
|
33
|
+
|
|
34
|
+
[tool.setuptools]
|
|
35
|
+
packages = ["sqsketch"]
|
|
36
|
+
|
|
37
|
+
[tool.pytest.ini_options]
|
|
38
|
+
testpaths = ["tests"]
|
|
39
|
+
addopts = "-q"
|
sqsketch-0.2.0/setup.cfg
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""sqsketch -- fixed-size, abundance-preserving sketches of count profiles.
|
|
2
|
+
|
|
3
|
+
The core is domain-agnostic: items are 64-bit codes. Adapters turn domain objects into
|
|
4
|
+
those codes.
|
|
5
|
+
|
|
6
|
+
from sqsketch import Sketch, Index # the core
|
|
7
|
+
from sqsketch import genomics # k-mers from FASTA
|
|
8
|
+
from sqsketch import llm # fingerprints of model output distributions
|
|
9
|
+
|
|
10
|
+
a = Sketch.from_dict({"apple": 12, "pear": 3}, D=1024)
|
|
11
|
+
b = Sketch.from_dense(probability_vector, D=1024)
|
|
12
|
+
a.similarity(b); a.hellinger(b); a.confidence_interval(b); a.kl_lower_bound(b)
|
|
13
|
+
a.merge(b)
|
|
14
|
+
"""
|
|
15
|
+
from .core import Sketch, Index
|
|
16
|
+
from .hashing import mix64, stable_code, stable_codes
|
|
17
|
+
from .baselines import topk_bc, topk_bytes
|
|
18
|
+
|
|
19
|
+
__version__ = "0.2.0"
|
|
20
|
+
__all__ = ["Sketch", "Index", "mix64", "stable_code", "stable_codes",
|
|
21
|
+
"topk_bc", "topk_bytes"]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""The incumbent a sketch has to beat, stated once so every benchmark uses the same one.
|
|
2
|
+
|
|
3
|
+
Keeping the k heaviest items and lumping the rest into a single bucket is what almost
|
|
4
|
+
every system already does, under one name or another: top-k logprobs, frequent-items
|
|
5
|
+
tables, truncated term vectors. It is simple, exact on the head, and blind to the tail.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
__all__ = ["topk_bc", "topk_bytes", "sketch_bytes", "effective_support", "tail_mass"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def topk_bytes(k: int, id_bits: int = 20, value_bits: int = 16) -> int:
|
|
15
|
+
"""Bytes to store k (id, value) pairs. Defaults suit a million-item alphabet."""
|
|
16
|
+
return int(np.ceil(k * (id_bits + value_bits) / 8))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def sketch_bytes(D: int, dtype=np.float32) -> int:
|
|
20
|
+
return int(D * np.dtype(dtype).itemsize)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def topk_bc(P, Q, k):
|
|
24
|
+
"""Bhattacharyya coefficient between two profiles, each truncated to its own top k.
|
|
25
|
+
|
|
26
|
+
Both sides keep one extra bucket for the mass they dropped, which is the fairest
|
|
27
|
+
version of the baseline: without it the truncated vectors would not be distributions.
|
|
28
|
+
"""
|
|
29
|
+
P = np.atleast_2d(np.asarray(P, dtype=np.float64))
|
|
30
|
+
Q = np.atleast_2d(np.asarray(Q, dtype=np.float64))
|
|
31
|
+
out = np.empty(P.shape[0])
|
|
32
|
+
for i in range(P.shape[0]):
|
|
33
|
+
k_i = min(k, P.shape[1] - 1)
|
|
34
|
+
tp = np.argpartition(-P[i], k_i)[:k_i]
|
|
35
|
+
tq = np.argpartition(-Q[i], k_i)[:k_i]
|
|
36
|
+
keys = np.union1d(tp, tq)
|
|
37
|
+
a, b = P[i][keys], Q[i][keys]
|
|
38
|
+
a = np.append(a, max(0.0, P[i].sum() - a.sum()))
|
|
39
|
+
b = np.append(b, max(0.0, Q[i].sum() - b.sum()))
|
|
40
|
+
sa, sb = a.sum(), b.sum()
|
|
41
|
+
a = a / (sa or 1.0)
|
|
42
|
+
b = b / (sb or 1.0)
|
|
43
|
+
out[i] = float(np.sum(np.sqrt(a * b)))
|
|
44
|
+
return out if out.size > 1 else float(out[0])
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def effective_support(P):
|
|
48
|
+
"""1 / sum(p_i^2): how many items are 'really' present. The first of the two numbers
|
|
49
|
+
that decide whether a sketch is the right tool."""
|
|
50
|
+
P = np.atleast_2d(np.asarray(P, dtype=np.float64))
|
|
51
|
+
P = P / P.sum(1, keepdims=True)
|
|
52
|
+
return 1.0 / np.sum(P ** 2, 1)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def tail_mass(P, k):
|
|
56
|
+
"""Mass left outside the top k. The second deciding number: a long tail is what
|
|
57
|
+
truncation cannot represent and a sketch can."""
|
|
58
|
+
P = np.atleast_2d(np.asarray(P, dtype=np.float64))
|
|
59
|
+
P = P / P.sum(1, keepdims=True)
|
|
60
|
+
kk = min(k, P.shape[1])
|
|
61
|
+
return 1.0 - np.sort(P, 1)[:, -kk:].sum(1)
|