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.
Files changed (27) hide show
  1. {fpembed-0.1.7 → fpembed-0.1.8}/PKG-INFO +202 -172
  2. fpembed-0.1.7/src/fpembed.egg-info/PKG-INFO → fpembed-0.1.8/README.md +420 -410
  3. {fpembed-0.1.7 → fpembed-0.1.8}/pyproject.toml +3 -3
  4. {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed/__init__.py +1 -1
  5. fpembed-0.1.7/README.md → fpembed-0.1.8/src/fpembed.egg-info/PKG-INFO +440 -390
  6. {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed.egg-info/requires.txt +1 -1
  7. {fpembed-0.1.7 → fpembed-0.1.8}/tests/test_compression.py +5 -5
  8. {fpembed-0.1.7 → fpembed-0.1.8}/tests/test_fingerprints.py +15 -76
  9. {fpembed-0.1.7 → fpembed-0.1.8}/tests/test_generator.py +1 -3
  10. {fpembed-0.1.7 → fpembed-0.1.8}/tests/test_properties.py +2 -2
  11. {fpembed-0.1.7 → fpembed-0.1.8}/tests/test_smiles_utils.py +5 -5
  12. {fpembed-0.1.7 → fpembed-0.1.8}/LICENSE +0 -0
  13. {fpembed-0.1.7 → fpembed-0.1.8}/setup.cfg +0 -0
  14. {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed/compression.py +0 -0
  15. {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed/compression_blockwise.py +0 -0
  16. {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed/compression_projection.py +0 -0
  17. {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed/generator.py +0 -0
  18. {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed/hashing.py +0 -0
  19. {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed/py.typed +0 -0
  20. {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed/smiles_utils.py +0 -0
  21. {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed.egg-info/SOURCES.txt +0 -0
  22. {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed.egg-info/dependency_links.txt +0 -0
  23. {fpembed-0.1.7 → fpembed-0.1.8}/src/fpembed.egg-info/top_level.txt +0 -0
  24. {fpembed-0.1.7 → fpembed-0.1.8}/tests/test_cache.py +0 -0
  25. {fpembed-0.1.7 → fpembed-0.1.8}/tests/test_compression_blockwise.py +0 -0
  26. {fpembed-0.1.7 → fpembed-0.1.8}/tests/test_compression_projection.py +0 -0
  27. {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.7
3
+ Version: 0.1.8
4
4
  Summary: Generalized fingerprint embedding library
5
5
  License-Expression: MIT
6
- Requires-Python: >=3.9
6
+ Requires-Python: >=3.10
7
7
  Description-Content-Type: text/markdown
8
8
  License-File: LICENSE
9
- Requires-Dist: scikit-fingerprints==2.0.0
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
  [![DOI](https://img.shields.io/badge/DOI-10.5281/zenodo.21447996-blue)](https://doi.org/10.5281/zenodo.21447996)
25
25
 
26
26
 
27
- A lightweight 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.
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
- ## Supported Fingerprint Types
80
+ ## Installation
51
81
 
52
- | Type | `fp_type` | Type-specific params |
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
- ## Compression Methods
84
+ ```bash
85
+ pip install fpembed
86
+ ```
66
87
 
67
- FPembed supports six compression methods, selectable via the `method` parameter on `EmbeddedFingerprintGenerator`. The default is `geometric`.
88
+ Install with demo app dependencies (nicegui, optuna, pandas, scikit-learn, etc.):
68
89
 
69
- ### Method Reference
90
+ ```bash
91
+ pip install fpembed[app]
92
+ ```
70
93
 
71
- | Method (`method` value) | Category | `method_params` | Dynamic Range / Distance Preservation | Complexity |
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
- ### The `method` Parameter
96
+ ```bash
97
+ pip install -e .
98
+ ```
81
99
 
82
- Pass `method` to the Generator constructor to select a compression strategy.
100
+ ### Conda Environment
83
101
 
84
- ### Method-Specific Parameters (`method_params`)
102
+ A full conda environment is provided for reproducibility:
85
103
 
86
- - **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.
87
- - **`hadamard`**: accepts `seed` (int, default `42`). Controls the random sign flips applied before the Fast Walsh-Hadamard Transform.
88
- - **`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.
104
+ ```bash
105
+ conda env create -f environment.yml
106
+ conda activate fpembed
107
+ ```
89
108
 
90
- Seed-based methods (`hadamard`, `random_projection`) are fully deterministic given the same seed and NumPy version. The default seed is `42`.
109
+ This installs all dependencies and the `fpembed` package in editable mode.
91
110
 
92
- ### Code Examples
111
+ ## Quick Start
112
+
113
+ ### Single Molecule (SMILES)
93
114
 
94
115
  ```python
95
116
  from fpembed import EmbeddedFingerprintGenerator
96
117
 
97
- # Geometric (default)
98
- gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2})
118
+ gen = EmbeddedFingerprintGenerator(
119
+ fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}
120
+ )
99
121
 
100
- # Linear weights
101
- gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}, method="linear")
122
+ # Generate compressed fingerprint from SMILES
123
+ emb = gen.GetFingerprintFromSmiles("CCO")
124
+ print(emb.shape) # (128,)
125
+ ```
102
126
 
103
- # Logarithmic weights
104
- gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}, method="log")
127
+ ### Different Fingerprint Types
105
128
 
106
- # Uniform weights (mean pooling)
107
- gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}, method="uniform")
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
- # Hadamard (SRHT)
110
- gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}, method="hadamard")
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
- # Random projection
113
- gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}, method="random_projection")
143
+ ### Single Molecule (SELFIES)
144
+
145
+ ```python
146
+ emb = gen.GetFingerprintFromSelfies("[C][C][O]")
147
+ print(emb.shape) # (128,)
114
148
  ```
115
149
 
116
- Bit-interleaving with a block-wise method:
150
+ ### Batch Processing
117
151
 
118
152
  ```python
119
- gen = EmbeddedFingerprintGenerator(
120
- fp_type="ecfp", fp_size=2048, compression=16,
121
- fp_params={"radius": 2}, method="linear",
122
- method_params={"interleave": True}
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 `compress_fingerprint` with a non-default method:
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, method="hadamard", method_params={"seed": 42})
177
+ emb = compress_fingerprint(fp, size=16)
134
178
  print(emb.shape) # (1, 128)
135
179
  ```
136
180
 
137
- ### Choosing a Method
181
+ ### Parameter Hashing
138
182
 
139
- 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.
183
+ ```python
184
+ from fpembed import fp_params_hash
140
185
 
141
- ### Performance Characteristics
186
+ h = fp_params_hash("ecfp", {"radius": 2})
187
+ print(h) # 16-char hex string, stable across sessions
188
+ ```
142
189
 
143
- All methods produce the same output dimensionality (D = L / compression) but differ in speed and memory:
190
+ ### Caching for Repeated Lookups
144
191
 
145
- | Method | Speed | Precomputed Memory | Best For |
146
- |--------|------------------------------------------------------------------------------------|--------------------------------------|----------|
147
- | Block-wise (all four) | Fastest - single vectorized einsum, O(L) | Negligible (C-length weight vector) | Default choice; large batches |
148
- | 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 |
149
- | Random projection | Fast - BLAS matmul, O(L D) | DxL matrix (~2 MB for L=2048, D=128) | Best theoretical guarantees (JL lemma) |
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
- 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.
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
- ## Project Structure
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
- ```bash
237
- pip install fpembed
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
- Install with demo app dependencies (nicegui, optuna, pandas, scikit-learn, etc.):
280
+ ## Compression Methods
241
281
 
242
- ```bash
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
- For development (editable install):
284
+ ### Method Reference
247
285
 
248
- ```bash
249
- pip install -e .
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
- ### Conda Environment
295
+ ### The `method` Parameter
253
296
 
254
- A full conda environment is provided for reproducibility:
297
+ Pass `method` to the Generator constructor to select a compression strategy.
255
298
 
256
- ```bash
257
- conda env create -f environment.yml
258
- conda activate fpembed
259
- ```
299
+ ### Method-Specific Parameters (`method_params`)
260
300
 
261
- This installs all dependencies and the `fpembed` package in editable mode.
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
- ## Quick Start
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
- ### Single Molecule (SMILES)
307
+ ### Code Examples
266
308
 
267
309
  ```python
268
310
  from fpembed import EmbeddedFingerprintGenerator
269
311
 
270
- gen = EmbeddedFingerprintGenerator(
271
- fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}
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
- ### Single Molecule (SELFIES)
315
+ # Linear weights
316
+ gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}, method="linear")
296
317
 
297
- ```python
298
- emb = gen.GetFingerprintFromSelfies("[C][C][O]")
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
- ### Batch Processing
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
- ```python
305
- smiles_list = ["CCO", "c1ccccc1", "CC(=O)O", "invalid_smiles"]
324
+ # Hadamard (SRHT)
325
+ gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}, method="hadamard")
306
326
 
307
- embeddings, invalid_indices = gen.GetFingerprintsFromSmiles(smiles_list)
308
- print(embeddings.shape) # (3, 128) - 3 valid molecules
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
- ### Raw Fingerprint (No Compression)
331
+ Bit-interleaving with a block-wise method:
313
332
 
314
333
  ```python
315
- gen_raw = EmbeddedFingerprintGenerator(
316
- fp_type="ecfp", fp_size=2048, compression=None, fp_params={"radius": 2}
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
- ### Standalone Compression Function
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
- ### Parameter Hashing
352
+ ### Choosing a Method
334
353
 
335
- ```python
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
- h = fp_params_hash("ecfp", {"radius": 2})
339
- print(h) # 16-char hex string, stable across sessions
340
- ```
356
+ ### Performance Characteristics
341
357
 
342
- ### Caching for Repeated Lookups
358
+ All methods produce the same output dimensionality (D = L / compression) but differ in speed and memory:
343
359
 
344
- ```python
345
- gen = EmbeddedFingerprintGenerator(
346
- fp_type="ecfp", fp_size=2048, compression=16,
347
- fp_params={"radius": 2}, cache_size=1024
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
- # First call computes and caches
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
- # Second call returns cached result
354
- emb2 = gen.GetFingerprintFromSmiles("CCO")
368
+ ## Project Structure
355
369
 
356
- print(gen.cache_info()) # CacheInfo(hits=1, misses=1, maxsize=1024, currsize=1)
357
- gen.clear_cache()
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