fpembed 0.1.7__tar.gz → 0.1.8__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.
- {fpembed-0.1.7 → fpembed-0.1.8}/PKG-INFO +202 -172
- fpembed-0.1.7/src/fpembed.egg-info/PKG-INFO → fpembed-0.1.8/README.md +420 -410
- {fpembed-0.1.7 → fpembed-0.1.8}/pyproject.toml +3 -3
- {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed/__init__.py +1 -1
- fpembed-0.1.7/README.md → fpembed-0.1.8/src/fpembed.egg-info/PKG-INFO +440 -390
- {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed.egg-info/requires.txt +1 -1
- {fpembed-0.1.7 → fpembed-0.1.8}/tests/test_compression.py +5 -5
- {fpembed-0.1.7 → fpembed-0.1.8}/tests/test_fingerprints.py +15 -76
- {fpembed-0.1.7 → fpembed-0.1.8}/tests/test_generator.py +1 -3
- {fpembed-0.1.7 → fpembed-0.1.8}/tests/test_properties.py +2 -2
- {fpembed-0.1.7 → fpembed-0.1.8}/tests/test_smiles_utils.py +5 -5
- {fpembed-0.1.7 → fpembed-0.1.8}/LICENSE +0 -0
- {fpembed-0.1.7 → fpembed-0.1.8}/setup.cfg +0 -0
- {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed/compression.py +0 -0
- {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed/compression_blockwise.py +0 -0
- {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed/compression_projection.py +0 -0
- {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed/generator.py +0 -0
- {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed/hashing.py +0 -0
- {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed/py.typed +0 -0
- {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed/smiles_utils.py +0 -0
- {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed.egg-info/SOURCES.txt +0 -0
- {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed.egg-info/dependency_links.txt +0 -0
- {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed.egg-info/top_level.txt +0 -0
- {fpembed-0.1.7 → fpembed-0.1.8}/tests/test_cache.py +0 -0
- {fpembed-0.1.7 → fpembed-0.1.8}/tests/test_compression_blockwise.py +0 -0
- {fpembed-0.1.7 → fpembed-0.1.8}/tests/test_compression_projection.py +0 -0
- {fpembed-0.1.7 → fpembed-0.1.8}/tests/test_hashing.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fpembed
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.8
|
|
4
4
|
Summary: Generalized fingerprint embedding library
|
|
5
5
|
License-Expression: MIT
|
|
6
|
-
Requires-Python: >=3.
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
License-File: LICENSE
|
|
9
|
-
Requires-Dist: scikit-fingerprints==2.
|
|
9
|
+
Requires-Dist: scikit-fingerprints==2.1.0
|
|
10
10
|
Requires-Dist: selfies>=2.2.0
|
|
11
11
|
Provides-Extra: app
|
|
12
12
|
Requires-Dist: nicegui>=3.8.0; extra == "app"
|
|
@@ -24,10 +24,40 @@ Dynamic: license-file
|
|
|
24
24
|
[](https://doi.org/10.5281/zenodo.21447996)
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
## Contents
|
|
28
|
+
|
|
29
|
+
- [Installation](#installation)
|
|
30
|
+
- [Conda Environment](#conda-environment)
|
|
31
|
+
- [Quick Start](#quick-start)
|
|
32
|
+
- [Why Use Embedded Fingerprints?](#why-use-embedded-fingerprints)
|
|
33
|
+
- [Storage Size](#storage-size)
|
|
34
|
+
- [ML Training and Inference Speed](#ml-training-and-inference-speed)
|
|
35
|
+
- [Memory During ML Training](#memory-during-ml-training)
|
|
36
|
+
- [Compression Overhead](#compression-overhead)
|
|
37
|
+
- [Sample Efficiency](#sample-efficiency)
|
|
38
|
+
- [Summary of Advantages](#summary-of-advantages)
|
|
39
|
+
- [Supported Fingerprint Types](#supported-fingerprint-types)
|
|
40
|
+
- [Compression Methods](#compression-methods)
|
|
41
|
+
- [Method Reference](#method-reference)
|
|
42
|
+
- [The `method` Parameter](#the-method-parameter)
|
|
43
|
+
- [Method-Specific Parameters (`method_params`)](#method-specific-parameters-method_params)
|
|
44
|
+
- [Code Examples](#code-examples)
|
|
45
|
+
- [Choosing a Method](#choosing-a-method)
|
|
46
|
+
- [Performance Characteristics](#performance-characteristics)
|
|
47
|
+
- [Project Structure](#project-structure)
|
|
48
|
+
- [Running the Demo App](#running-the-demo-app)
|
|
49
|
+
- [Datasets](#datasets)
|
|
50
|
+
- [License](#license)
|
|
51
|
+
- [Legal & Trademark Notice](#legal--trademark-notice)
|
|
52
|
+
- [AI disclosure](#ai-disclosure)
|
|
53
|
+
- [Support](#support)
|
|
54
|
+
|
|
55
|
+
A Python package for generating compressed molecular fingerprint embeddings, backed by [scikit-fingerprints](https://github.com/scikit-fingerprints/scikit-fingerprints). Supports ten binary fingerprint types through a single unified class.
|
|
28
56
|
|
|
29
57
|
FPembed compresses standard molecular fingerprints using weighted binary masking, producing compact float vectors suitable for machine-learning models. The package accepts SMILES, SELFIES, and RDKit Mol objects as input.
|
|
30
58
|
|
|
59
|
+
At a glance: roughly 16x smaller feature vectors, comparable accuracy to raw fingerprints, and a drop-in fit for any scikit-learn model. If you just want to try it, jump to [Quick Start](#quick-start); if you want the reasoning first, read [Why Use Embedded Fingerprints?](#why-use-embedded-fingerprints) below.
|
|
60
|
+
|
|
31
61
|
The concept of compressing molecular fingerprints via weighted binary masking was originally introduced for Morgan fingerprints in the eMFP paper:
|
|
32
62
|
|
|
33
63
|
> Nuñez-Andrade, E. A., Vidal-Daza, I., Gomez-Bombarelli, R., Ryan, J. W., & Martin-Martinez, F. J. (2025).
|
|
@@ -47,108 +77,133 @@ The concept of compressing molecular fingerprints via weighted binary masking wa
|
|
|
47
77
|
|
|
48
78
|
Original concept repository: [MMLabCodes/eMFP](https://github.com/MMLabCodes/eMFP)
|
|
49
79
|
|
|
50
|
-
##
|
|
80
|
+
## Installation
|
|
51
81
|
|
|
52
|
-
|
|
53
|
-
|------|-----------|---------------------|
|
|
54
|
-
| Extended Connectivity (ECFP) | `ecfp` | `radius` (default 2) |
|
|
55
|
-
| Atom Pair | `atom_pair` | `min_distance` (1), `max_distance` (30) |
|
|
56
|
-
| Topological Torsion | `topological_torsion` | `torsion_atom_count` (4) |
|
|
57
|
-
| RDKit | `rdkit` | `min_path` (1), `max_path` (7) |
|
|
58
|
-
| Layered | `layered` | `min_path` (1), `max_path` (7) |
|
|
59
|
-
| Pattern | `pattern` | (none) |
|
|
60
|
-
| Avalon | `avalon` | (none) |
|
|
61
|
-
| SECFP | `secfp` | `radius` (default 3) |
|
|
62
|
-
| MHFP | `mhfp` | `radius` (default 3) |
|
|
63
|
-
| MAP4 | `map` | `radius` (default 2) |
|
|
82
|
+
Install the core package (rdkit, numpy, selfies, scikit-fingerprints):
|
|
64
83
|
|
|
65
|
-
|
|
84
|
+
```bash
|
|
85
|
+
pip install fpembed
|
|
86
|
+
```
|
|
66
87
|
|
|
67
|
-
|
|
88
|
+
Install with demo app dependencies (nicegui, optuna, pandas, scikit-learn, etc.):
|
|
68
89
|
|
|
69
|
-
|
|
90
|
+
```bash
|
|
91
|
+
pip install fpembed[app]
|
|
92
|
+
```
|
|
70
93
|
|
|
71
|
-
|
|
72
|
-
|-------------------------|----------|-----------------|---------------------------------------|------------|
|
|
73
|
-
| `geometric` | block-wise | `interleave` (bool) | 65,536:1 dynamic range | O(L) |
|
|
74
|
-
| `linear` | block-wise | `interleave` (bool) | S:1 dynamic range | O(L) |
|
|
75
|
-
| `log` | block-wise | `interleave` (bool) | ~4.1:1 dynamic range | O(L) |
|
|
76
|
-
| `uniform` | block-wise | `interleave` (bool) | 1:1 (mean pooling) | O(L) |
|
|
77
|
-
| `hadamard` | global | `seed` (int) | orthogonal projection | O(L log L) |
|
|
78
|
-
| `random_projection` | global | `seed` (int) + `sparse` (bool) | JL distance preservation | O(L D) |
|
|
94
|
+
For development (editable install):
|
|
79
95
|
|
|
80
|
-
|
|
96
|
+
```bash
|
|
97
|
+
pip install -e .
|
|
98
|
+
```
|
|
81
99
|
|
|
82
|
-
|
|
100
|
+
### Conda Environment
|
|
83
101
|
|
|
84
|
-
|
|
102
|
+
A full conda environment is provided for reproducibility:
|
|
85
103
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
104
|
+
```bash
|
|
105
|
+
conda env create -f environment.yml
|
|
106
|
+
conda activate fpembed
|
|
107
|
+
```
|
|
89
108
|
|
|
90
|
-
|
|
109
|
+
This installs all dependencies and the `fpembed` package in editable mode.
|
|
91
110
|
|
|
92
|
-
|
|
111
|
+
## Quick Start
|
|
112
|
+
|
|
113
|
+
### Single Molecule (SMILES)
|
|
93
114
|
|
|
94
115
|
```python
|
|
95
116
|
from fpembed import EmbeddedFingerprintGenerator
|
|
96
117
|
|
|
97
|
-
|
|
98
|
-
|
|
118
|
+
gen = EmbeddedFingerprintGenerator(
|
|
119
|
+
fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}
|
|
120
|
+
)
|
|
99
121
|
|
|
100
|
-
#
|
|
101
|
-
|
|
122
|
+
# Generate compressed fingerprint from SMILES
|
|
123
|
+
emb = gen.GetFingerprintFromSmiles("CCO")
|
|
124
|
+
print(emb.shape) # (128,)
|
|
125
|
+
```
|
|
102
126
|
|
|
103
|
-
|
|
104
|
-
gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}, method="log")
|
|
127
|
+
### Different Fingerprint Types
|
|
105
128
|
|
|
106
|
-
|
|
107
|
-
|
|
129
|
+
```python
|
|
130
|
+
# Atom Pair fingerprint
|
|
131
|
+
gen_ap = EmbeddedFingerprintGenerator(
|
|
132
|
+
fp_type="atom_pair", fp_size=2048, compression=16,
|
|
133
|
+
fp_params={"min_distance": 1, "max_distance": 30}
|
|
134
|
+
)
|
|
108
135
|
|
|
109
|
-
#
|
|
110
|
-
|
|
136
|
+
# Topological Torsion fingerprint
|
|
137
|
+
gen_tt = EmbeddedFingerprintGenerator(
|
|
138
|
+
fp_type="topological_torsion", fp_size=2048, compression=16,
|
|
139
|
+
fp_params={"torsion_atom_count": 4}
|
|
140
|
+
)
|
|
141
|
+
```
|
|
111
142
|
|
|
112
|
-
|
|
113
|
-
|
|
143
|
+
### Single Molecule (SELFIES)
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
emb = gen.GetFingerprintFromSelfies("[C][C][O]")
|
|
147
|
+
print(emb.shape) # (128,)
|
|
114
148
|
```
|
|
115
149
|
|
|
116
|
-
|
|
150
|
+
### Batch Processing
|
|
117
151
|
|
|
118
152
|
```python
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
153
|
+
smiles_list = ["CCO", "c1ccccc1", "CC(=O)O", "invalid_smiles"]
|
|
154
|
+
|
|
155
|
+
embeddings, invalid_indices = gen.GetFingerprintsFromSmiles(smiles_list)
|
|
156
|
+
print(embeddings.shape) # (3, 128) - 3 valid molecules
|
|
157
|
+
print(invalid_indices) # [3] - index of invalid SMILES
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Raw Fingerprint (No Compression)
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
gen_raw = EmbeddedFingerprintGenerator(
|
|
164
|
+
fp_type="ecfp", fp_size=2048, compression=None, fp_params={"radius": 2}
|
|
123
165
|
)
|
|
166
|
+
fp = gen_raw.GetFingerprintFromSmiles("CCO")
|
|
167
|
+
print(fp.shape) # (2048,)
|
|
124
168
|
```
|
|
125
169
|
|
|
126
|
-
Standalone
|
|
170
|
+
### Standalone Compression Function
|
|
127
171
|
|
|
128
172
|
```python
|
|
129
|
-
from fpembed import compress_fingerprint
|
|
130
173
|
import numpy as np
|
|
174
|
+
from fpembed import compress_fingerprint
|
|
131
175
|
|
|
132
176
|
fp = np.random.randint(0, 2, size=2048).astype(np.float64)
|
|
133
|
-
emb = compress_fingerprint(fp, size=16
|
|
177
|
+
emb = compress_fingerprint(fp, size=16)
|
|
134
178
|
print(emb.shape) # (1, 128)
|
|
135
179
|
```
|
|
136
180
|
|
|
137
|
-
###
|
|
181
|
+
### Parameter Hashing
|
|
138
182
|
|
|
139
|
-
|
|
183
|
+
```python
|
|
184
|
+
from fpembed import fp_params_hash
|
|
140
185
|
|
|
141
|
-
|
|
186
|
+
h = fp_params_hash("ecfp", {"radius": 2})
|
|
187
|
+
print(h) # 16-char hex string, stable across sessions
|
|
188
|
+
```
|
|
142
189
|
|
|
143
|
-
|
|
190
|
+
### Caching for Repeated Lookups
|
|
144
191
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
192
|
+
```python
|
|
193
|
+
gen = EmbeddedFingerprintGenerator(
|
|
194
|
+
fp_type="ecfp", fp_size=2048, compression=16,
|
|
195
|
+
fp_params={"radius": 2}, cache_size=1024
|
|
196
|
+
)
|
|
150
197
|
|
|
151
|
-
|
|
198
|
+
# First call computes and caches
|
|
199
|
+
emb = gen.GetFingerprintFromSmiles("CCO")
|
|
200
|
+
|
|
201
|
+
# Second call returns cached result
|
|
202
|
+
emb2 = gen.GetFingerprintFromSmiles("CCO")
|
|
203
|
+
|
|
204
|
+
print(gen.cache_info()) # CacheInfo(hits=1, misses=1, maxsize=1024, currsize=1)
|
|
205
|
+
gen.clear_cache()
|
|
206
|
+
```
|
|
152
207
|
|
|
153
208
|
## Why Use Embedded Fingerprints?
|
|
154
209
|
|
|
@@ -207,154 +262,129 @@ High-dimensional spaces (2048 binary features) suffer from the curse of dimensio
|
|
|
207
262
|
|
|
208
263
|
The choice between raw and embedded fingerprints is a classic accuracy-vs-efficiency tradeoff. Embedded fingerprints sacrifice a small amount of information for dramatic improvements in storage, speed, and memory - making them the practical default for most molecular ML workflows.
|
|
209
264
|
|
|
210
|
-
##
|
|
211
|
-
|
|
212
|
-
```
|
|
213
|
-
fpembed/
|
|
214
|
-
├── src/fpembed/ # pip-distributable package
|
|
215
|
-
│ ├── __init__.py
|
|
216
|
-
│ ├── generator.py # EmbeddedFingerprintGenerator
|
|
217
|
-
│ ├── compression.py # compress_fingerprint (orchestrator)
|
|
218
|
-
│ ├── compression_blockwise.py # block-wise weight schemes
|
|
219
|
-
│ ├── compression_projection.py # Hadamard SRHT + random projection
|
|
220
|
-
│ ├── smiles_utils.py # parse_smiles, canonicalize_smiles
|
|
221
|
-
│ ├── hashing.py # fp_params_hash
|
|
222
|
-
│ └── py.typed # PEP 561 marker
|
|
223
|
-
├── examples/
|
|
224
|
-
│ ├── quickstart.ipynb # usage notebook
|
|
225
|
-
│ ├── datasets/ # molecular datasets (RedDB, NFA, QM9)
|
|
226
|
-
│ └── nicegui_app/ # NiceGUI demo application
|
|
227
|
-
├── pyproject.toml
|
|
228
|
-
├── environment.yml
|
|
229
|
-
└── README.md
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
## Installation
|
|
233
|
-
|
|
234
|
-
Install the core package (rdkit, numpy, selfies, scikit-fingerprints):
|
|
265
|
+
## Supported Fingerprint Types
|
|
235
266
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
267
|
+
| Type | `fp_type` | Type-specific params |
|
|
268
|
+
|------|-----------|---------------------|
|
|
269
|
+
| Extended Connectivity (ECFP) | `ecfp` | `radius` (default 2) |
|
|
270
|
+
| Atom Pair | `atom_pair` | `min_distance` (1), `max_distance` (30) |
|
|
271
|
+
| Topological Torsion | `topological_torsion` | `torsion_atom_count` (4) |
|
|
272
|
+
| RDKit | `rdkit` | `min_path` (1), `max_path` (7) |
|
|
273
|
+
| Layered | `layered` | `min_path` (1), `max_path` (7) |
|
|
274
|
+
| Pattern | `pattern` | (none) |
|
|
275
|
+
| Avalon | `avalon` | (none) |
|
|
276
|
+
| SECFP | `secfp` | `radius` (default 3) |
|
|
277
|
+
| MHFP | `mhfp` | `radius` (default 3) |
|
|
278
|
+
| MAP4 | `map` | `radius` (default 2) |
|
|
239
279
|
|
|
240
|
-
|
|
280
|
+
## Compression Methods
|
|
241
281
|
|
|
242
|
-
|
|
243
|
-
pip install fpembed[app]
|
|
244
|
-
```
|
|
282
|
+
FPembed supports six compression methods, selectable via the `method` parameter on `EmbeddedFingerprintGenerator`. The default is `geometric`.
|
|
245
283
|
|
|
246
|
-
|
|
284
|
+
### Method Reference
|
|
247
285
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
286
|
+
| Method (`method` value) | Category | `method_params` | Dynamic Range / Distance Preservation | Complexity |
|
|
287
|
+
|-------------------------|----------|-----------------|---------------------------------------|------------|
|
|
288
|
+
| `geometric` | block-wise | `interleave` (bool) | 65,536:1 dynamic range | O(L) |
|
|
289
|
+
| `linear` | block-wise | `interleave` (bool) | S:1 dynamic range | O(L) |
|
|
290
|
+
| `log` | block-wise | `interleave` (bool) | ~4.1:1 dynamic range | O(L) |
|
|
291
|
+
| `uniform` | block-wise | `interleave` (bool) | 1:1 (mean pooling) | O(L) |
|
|
292
|
+
| `hadamard` | global | `seed` (int) | orthogonal projection | O(L log L) |
|
|
293
|
+
| `random_projection` | global | `seed` (int) + `sparse` (bool) | JL distance preservation | O(L D) |
|
|
251
294
|
|
|
252
|
-
###
|
|
295
|
+
### The `method` Parameter
|
|
253
296
|
|
|
254
|
-
|
|
297
|
+
Pass `method` to the Generator constructor to select a compression strategy.
|
|
255
298
|
|
|
256
|
-
|
|
257
|
-
conda env create -f environment.yml
|
|
258
|
-
conda activate fpembed
|
|
259
|
-
```
|
|
299
|
+
### Method-Specific Parameters (`method_params`)
|
|
260
300
|
|
|
261
|
-
|
|
301
|
+
- **Block-wise methods** (`geometric`, `linear`, `log`, `uniform`): accept `interleave` (bool, default `False`). When `True`, bits are assigned to blocks by stride (`bit[i] -> block[i % n_blocks]`) instead of contiguous partitioning, breaking hash clustering artifacts.
|
|
302
|
+
- **`hadamard`**: accepts `seed` (int, default `42`). Controls the random sign flips applied before the Fast Walsh-Hadamard Transform.
|
|
303
|
+
- **`random_projection`**: accepts `seed` (int, default `42`) and `sparse` (bool, default `False`). The `sparse` option uses the Achlioptas variant with approximately 2/3 zero entries for faster computation at comparable quality.
|
|
262
304
|
|
|
263
|
-
|
|
305
|
+
Seed-based methods (`hadamard`, `random_projection`) are fully deterministic given the same seed and NumPy version. The default seed is `42`.
|
|
264
306
|
|
|
265
|
-
###
|
|
307
|
+
### Code Examples
|
|
266
308
|
|
|
267
309
|
```python
|
|
268
310
|
from fpembed import EmbeddedFingerprintGenerator
|
|
269
311
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
)
|
|
273
|
-
|
|
274
|
-
# Generate compressed fingerprint from SMILES
|
|
275
|
-
emb = gen.GetFingerprintFromSmiles("CCO")
|
|
276
|
-
print(emb.shape) # (128,)
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
### Different Fingerprint Types
|
|
280
|
-
|
|
281
|
-
```python
|
|
282
|
-
# Atom Pair fingerprint
|
|
283
|
-
gen_ap = EmbeddedFingerprintGenerator(
|
|
284
|
-
fp_type="atom_pair", fp_size=2048, compression=16,
|
|
285
|
-
fp_params={"min_distance": 1, "max_distance": 30}
|
|
286
|
-
)
|
|
287
|
-
|
|
288
|
-
# Topological Torsion fingerprint
|
|
289
|
-
gen_tt = EmbeddedFingerprintGenerator(
|
|
290
|
-
fp_type="topological_torsion", fp_size=2048, compression=16,
|
|
291
|
-
fp_params={"torsion_atom_count": 4}
|
|
292
|
-
)
|
|
293
|
-
```
|
|
312
|
+
# Geometric (default)
|
|
313
|
+
gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2})
|
|
294
314
|
|
|
295
|
-
|
|
315
|
+
# Linear weights
|
|
316
|
+
gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}, method="linear")
|
|
296
317
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
print(emb.shape) # (128,)
|
|
300
|
-
```
|
|
318
|
+
# Logarithmic weights
|
|
319
|
+
gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}, method="log")
|
|
301
320
|
|
|
302
|
-
|
|
321
|
+
# Uniform weights (mean pooling)
|
|
322
|
+
gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}, method="uniform")
|
|
303
323
|
|
|
304
|
-
|
|
305
|
-
|
|
324
|
+
# Hadamard (SRHT)
|
|
325
|
+
gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}, method="hadamard")
|
|
306
326
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
print(invalid_indices) # [3] - index of invalid SMILES
|
|
327
|
+
# Random projection
|
|
328
|
+
gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}, method="random_projection")
|
|
310
329
|
```
|
|
311
330
|
|
|
312
|
-
|
|
331
|
+
Bit-interleaving with a block-wise method:
|
|
313
332
|
|
|
314
333
|
```python
|
|
315
|
-
|
|
316
|
-
fp_type="ecfp", fp_size=2048, compression=
|
|
334
|
+
gen = EmbeddedFingerprintGenerator(
|
|
335
|
+
fp_type="ecfp", fp_size=2048, compression=16,
|
|
336
|
+
fp_params={"radius": 2}, method="linear",
|
|
337
|
+
method_params={"interleave": True}
|
|
317
338
|
)
|
|
318
|
-
fp = gen_raw.GetFingerprintFromSmiles("CCO")
|
|
319
|
-
print(fp.shape) # (2048,)
|
|
320
339
|
```
|
|
321
340
|
|
|
322
|
-
|
|
341
|
+
Standalone `compress_fingerprint` with a non-default method:
|
|
323
342
|
|
|
324
343
|
```python
|
|
325
|
-
import numpy as np
|
|
326
344
|
from fpembed import compress_fingerprint
|
|
345
|
+
import numpy as np
|
|
327
346
|
|
|
328
347
|
fp = np.random.randint(0, 2, size=2048).astype(np.float64)
|
|
329
|
-
emb = compress_fingerprint(fp, size=16)
|
|
348
|
+
emb = compress_fingerprint(fp, size=16, method="hadamard", method_params={"seed": 42})
|
|
330
349
|
print(emb.shape) # (1, 128)
|
|
331
350
|
```
|
|
332
351
|
|
|
333
|
-
###
|
|
352
|
+
### Choosing a Method
|
|
334
353
|
|
|
335
|
-
|
|
336
|
-
from fpembed import fp_params_hash
|
|
354
|
+
Block-wise methods (`geometric`, `linear`, `log`, `uniform`) are fast (O(L)) and simple - use them when speed matters or compression ratios are modest. Among these, `geometric` preserves the most dynamic range while `uniform` treats all bits equally (mean pooling). Global projection methods (`hadamard`, `random_projection`) mix information across all input bits, which helps retain more information at high compression ratios. `hadamard` is efficient (O(L log L)) and requires power-of-2 fingerprint sizes; `random_projection` offers the strongest theoretical distance-preservation guarantees (Johnson-Lindenstrauss lemma) at the cost of O(L D) complexity.
|
|
337
355
|
|
|
338
|
-
|
|
339
|
-
print(h) # 16-char hex string, stable across sessions
|
|
340
|
-
```
|
|
356
|
+
### Performance Characteristics
|
|
341
357
|
|
|
342
|
-
|
|
358
|
+
All methods produce the same output dimensionality (D = L / compression) but differ in speed and memory:
|
|
343
359
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
)
|
|
360
|
+
| Method | Speed | Precomputed Memory | Best For |
|
|
361
|
+
|--------|------------------------------------------------------------------------------------|--------------------------------------|----------|
|
|
362
|
+
| Block-wise (all four) | Fastest - single vectorized einsum, O(L) | Negligible (C-length weight vector) | Default choice; large batches |
|
|
363
|
+
| Hadamard (SRHT) | Fast - vectorized FWHT (NumPy reshape/broadcast, no Python-level loop), O(L log L) | L-length sign vector (~16 KB) | Large batches at high compression ratios |
|
|
364
|
+
| Random projection | Fast - BLAS matmul, O(L D) | DxL matrix (~2 MB for L=2048, D=128) | Best theoretical guarantees (JL lemma) |
|
|
349
365
|
|
|
350
|
-
|
|
351
|
-
emb = gen.GetFingerprintFromSmiles("CCO")
|
|
366
|
+
Block-wise methods remain the fastest choice for large batches. Hadamard's FWHT is vectorized across the whole batch and works much faster now. Random projection's memory cost grows quadratically with fingerprint size.
|
|
352
367
|
|
|
353
|
-
|
|
354
|
-
emb2 = gen.GetFingerprintFromSmiles("CCO")
|
|
368
|
+
## Project Structure
|
|
355
369
|
|
|
356
|
-
|
|
357
|
-
|
|
370
|
+
```
|
|
371
|
+
fpembed/
|
|
372
|
+
├── src/fpembed/ # pip-distributable package
|
|
373
|
+
│ ├── __init__.py
|
|
374
|
+
│ ├── generator.py # EmbeddedFingerprintGenerator
|
|
375
|
+
│ ├── compression.py # compress_fingerprint (orchestrator)
|
|
376
|
+
│ ├── compression_blockwise.py # block-wise weight schemes
|
|
377
|
+
│ ├── compression_projection.py # Hadamard SRHT + random projection
|
|
378
|
+
│ ├── smiles_utils.py # parse_smiles, canonicalize_smiles
|
|
379
|
+
│ ├── hashing.py # fp_params_hash
|
|
380
|
+
│ └── py.typed # PEP 561 marker
|
|
381
|
+
├── examples/
|
|
382
|
+
│ ├── quickstart.ipynb # usage notebook
|
|
383
|
+
│ ├── datasets/ # molecular datasets (RedDB, NFA, QM9)
|
|
384
|
+
│ └── nicegui_app/ # NiceGUI demo application
|
|
385
|
+
├── pyproject.toml
|
|
386
|
+
├── environment.yml
|
|
387
|
+
└── README.md
|
|
358
388
|
```
|
|
359
389
|
|
|
360
390
|
## Running the Demo App
|