fpembed 0.1.0__tar.gz → 0.1.2__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 (29) hide show
  1. fpembed-0.1.2/PKG-INFO +396 -0
  2. {fpembed-0.1.0 → fpembed-0.1.2}/pyproject.toml +3 -2
  3. fpembed-0.1.2/src/fpembed.egg-info/PKG-INFO +396 -0
  4. {fpembed-0.1.0 → fpembed-0.1.2}/src/fpembed.egg-info/requires.txt +1 -1
  5. fpembed-0.1.0/PKG-INFO +0 -20
  6. fpembed-0.1.0/src/fpembed.egg-info/PKG-INFO +0 -20
  7. {fpembed-0.1.0 → fpembed-0.1.2}/LICENSE +0 -0
  8. {fpembed-0.1.0 → fpembed-0.1.2}/README.md +0 -0
  9. {fpembed-0.1.0 → fpembed-0.1.2}/setup.cfg +0 -0
  10. {fpembed-0.1.0 → fpembed-0.1.2}/src/fpembed/__init__.py +0 -0
  11. {fpembed-0.1.0 → fpembed-0.1.2}/src/fpembed/compression.py +0 -0
  12. {fpembed-0.1.0 → fpembed-0.1.2}/src/fpembed/compression_blockwise.py +0 -0
  13. {fpembed-0.1.0 → fpembed-0.1.2}/src/fpembed/compression_projection.py +0 -0
  14. {fpembed-0.1.0 → fpembed-0.1.2}/src/fpembed/generator.py +0 -0
  15. {fpembed-0.1.0 → fpembed-0.1.2}/src/fpembed/hashing.py +0 -0
  16. {fpembed-0.1.0 → fpembed-0.1.2}/src/fpembed/py.typed +0 -0
  17. {fpembed-0.1.0 → fpembed-0.1.2}/src/fpembed/smiles_utils.py +0 -0
  18. {fpembed-0.1.0 → fpembed-0.1.2}/src/fpembed.egg-info/SOURCES.txt +0 -0
  19. {fpembed-0.1.0 → fpembed-0.1.2}/src/fpembed.egg-info/dependency_links.txt +0 -0
  20. {fpembed-0.1.0 → fpembed-0.1.2}/src/fpembed.egg-info/top_level.txt +0 -0
  21. {fpembed-0.1.0 → fpembed-0.1.2}/tests/test_cache.py +0 -0
  22. {fpembed-0.1.0 → fpembed-0.1.2}/tests/test_compression.py +0 -0
  23. {fpembed-0.1.0 → fpembed-0.1.2}/tests/test_compression_blockwise.py +0 -0
  24. {fpembed-0.1.0 → fpembed-0.1.2}/tests/test_compression_projection.py +0 -0
  25. {fpembed-0.1.0 → fpembed-0.1.2}/tests/test_fingerprints.py +0 -0
  26. {fpembed-0.1.0 → fpembed-0.1.2}/tests/test_generator.py +0 -0
  27. {fpembed-0.1.0 → fpembed-0.1.2}/tests/test_hashing.py +0 -0
  28. {fpembed-0.1.0 → fpembed-0.1.2}/tests/test_properties.py +0 -0
  29. {fpembed-0.1.0 → fpembed-0.1.2}/tests/test_smiles_utils.py +0 -0
fpembed-0.1.2/PKG-INFO ADDED
@@ -0,0 +1,396 @@
1
+ Metadata-Version: 2.4
2
+ Name: fpembed
3
+ Version: 0.1.2
4
+ Summary: Generalized fingerprint embedding library
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: rdkit==2025.9.3
10
+ Requires-Dist: numpy==2.2.6
11
+ Requires-Dist: selfies==1.0.2
12
+ Requires-Dist: scikit-fingerprints==2.0.0
13
+ Provides-Extra: app
14
+ Requires-Dist: nicegui>=3.8.0; extra == "app"
15
+ Requires-Dist: optuna>=3.1.0; extra == "app"
16
+ Requires-Dist: pyyaml>=6.0; extra == "app"
17
+ Requires-Dist: altair>=5.0; extra == "app"
18
+ Requires-Dist: pandas>=1.5.2; extra == "app"
19
+ Requires-Dist: scikit-learn>=1.2.0; extra == "app"
20
+ Requires-Dist: tqdm>=4.64.1; extra == "app"
21
+ Dynamic: license-file
22
+
23
+ # FPembed - Generalized Molecular Fingerprint Embeddings
24
+
25
+ A lightweight Python package for generating compressed molecular fingerprint embeddings, backed by [scikit-fingerprints](https://github.com/scikit-fingerprints/scikit-fingerprints). Supports six binary fingerprint types through a single unified class.
26
+
27
+ 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.
28
+
29
+ The concept of compressing molecular fingerprints via weighted binary masking was originally introduced for Morgan fingerprints in the eMFP paper:
30
+
31
+ > Nuñez-Andrade, E. A., Vidal-Daza, I., Gomez-Bombarelli, R., Ryan, J. W., & Martin-Martinez, F. J. (2025).
32
+ > Embedded Morgan Fingerprints for more efficient molecular property predictions with machine learning.
33
+ > *ChemRxiv* (preprint). https://doi.org/10.26434/chemrxiv-2025-6hfp8
34
+
35
+ ```bibtex
36
+ @article{nunez2025emfp,
37
+ author = {Nu{\~n}ez-Andrade, Emilio A. and Vidal-Daza, Isaac and Gomez-Bombarelli, Rafael and Ryan, James W. and Martin-Martinez, Francisco J.},
38
+ title = {Embedded {Morgan} Fingerprints for more efficient molecular property predictions with machine learning},
39
+ journal = {ChemRxiv},
40
+ year = {2025},
41
+ doi = {10.26434/chemrxiv-2025-6hfp8},
42
+ note = {Preprint}
43
+ }
44
+ ```
45
+
46
+ Original concept repository: [MMLabCodes/eMFP](https://github.com/MMLabCodes/eMFP)
47
+
48
+ ## Supported Fingerprint Types
49
+
50
+ | Type | `fp_type` | Type-specific params |
51
+ |------|-----------|---------------------|
52
+ | Extended Connectivity (ECFP) | `ecfp` | `radius` (default 2) |
53
+ | Atom Pair | `atom_pair` | `min_distance` (1), `max_distance` (30) |
54
+ | Topological Torsion | `topological_torsion` | `torsion_atom_count` (4) |
55
+ | RDKit | `rdkit` | `min_path` (1), `max_path` (7) |
56
+ | Layered | `layered` | `min_path` (1), `max_path` (7) |
57
+ | Pattern | `pattern` | (none) |
58
+
59
+ ## Compression Methods
60
+
61
+ FPembed supports six compression methods, selectable via the `method` parameter on `EmbeddedFingerprintGenerator`. The default is `geometric`.
62
+
63
+ ### Method Reference
64
+
65
+ | Method (`method` value) | Category | `method_params` | Dynamic Range / Distance Preservation | Complexity |
66
+ |-------------------------|----------|-----------------|---------------------------------------|------------|
67
+ | `geometric` | block-wise | `interleave` (bool) | 65,536:1 dynamic range | O(L) |
68
+ | `linear` | block-wise | `interleave` (bool) | S:1 dynamic range | O(L) |
69
+ | `log` | block-wise | `interleave` (bool) | ~4.1:1 dynamic range | O(L) |
70
+ | `uniform` | block-wise | `interleave` (bool) | 1:1 (mean pooling) | O(L) |
71
+ | `hadamard` | global | `seed` (int) | orthogonal projection | O(L log L) |
72
+ | `random_projection` | global | `seed` (int) + `sparse` (bool) | JL distance preservation | O(L·D) |
73
+
74
+ ### The `method` Parameter
75
+
76
+ Pass `method` to the Generator constructor to select a compression strategy.
77
+
78
+ ### Method-Specific Parameters (`method_params`)
79
+
80
+ - **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.
81
+ - **`hadamard`**: accepts `seed` (int, default `42`). Controls the random sign flips applied before the Fast Walsh-Hadamard Transform.
82
+ - **`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.
83
+
84
+ Seed-based methods (`hadamard`, `random_projection`) are fully deterministic given the same seed and NumPy version. The default seed is `42`.
85
+
86
+ ### Code Examples
87
+
88
+ ```python
89
+ from fpembed import EmbeddedFingerprintGenerator
90
+
91
+ # Geometric (default)
92
+ gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2})
93
+
94
+ # Linear weights
95
+ gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}, method="linear")
96
+
97
+ # Logarithmic weights
98
+ gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}, method="log")
99
+
100
+ # Uniform weights (mean pooling)
101
+ gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}, method="uniform")
102
+
103
+ # Hadamard (SRHT)
104
+ gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}, method="hadamard")
105
+
106
+ # Random projection
107
+ gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}, method="random_projection")
108
+ ```
109
+
110
+ Bit-interleaving with a block-wise method:
111
+
112
+ ```python
113
+ gen = EmbeddedFingerprintGenerator(
114
+ fp_type="ecfp", fp_size=2048, compression=16,
115
+ fp_params={"radius": 2}, method="linear",
116
+ method_params={"interleave": True}
117
+ )
118
+ ```
119
+
120
+ Standalone `compress_fingerprint` with a non-default method:
121
+
122
+ ```python
123
+ from fpembed import compress_fingerprint
124
+ import numpy as np
125
+
126
+ fp = np.random.randint(0, 2, size=2048).astype(np.float64)
127
+ emb = compress_fingerprint(fp, size=16, method="hadamard", method_params={"seed": 42})
128
+ print(emb.shape) # (1, 128)
129
+ ```
130
+
131
+ ### Choosing a Method
132
+
133
+ 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.
134
+
135
+ ### Performance Characteristics
136
+
137
+ All methods produce the same output dimensionality (D = L / compression) but differ in speed and memory:
138
+
139
+ | Method | Speed | Precomputed Memory | Best For |
140
+ |--------|------------------------------------------|--------------------------------------|----------|
141
+ | Block-wise (all four) | Fastest - single vectorized einsum, O(L) | Negligible (C-length weight vector) | Default choice; large batches |
142
+ | Random projection | Fast - BLAS matmul, O(L·D) | DxL matrix (~2 MB for L=2048, D=128) | Best theoretical guarantees (JL lemma) |
143
+ | Hadamard (SRHT) | Slowest - pure-Python FWHT, O(L log L) | L-length sign vector (~16 KB) | Small-scale experiments; future optimization |
144
+
145
+ Block-wise methods are ~2–5x faster than random projection and orders of magnitude faster than Hadamard in practice. Random projection's memory cost grows quadratically with fingerprint size.
146
+
147
+ ## Why Use Embedded Fingerprints?
148
+
149
+ Predictive accuracy is one axis of comparison between raw and embedded fingerprints - and the gap can be narrow, especially on large datasets where raw fingerprints have enough data to exploit all 2048 bits directly. However, accuracy is not the only metric that matters. Embedded fingerprints offer substantial, guaranteed advantages on every operational dimension: storage, speed, memory, and sample efficiency.
150
+
151
+ The core argument is not "embedded fingerprints are always more accurate" but rather "embedded fingerprints achieve comparable accuracy at a fraction of the computational cost."
152
+
153
+ ### Storage Size
154
+
155
+ This is the most clear-cut advantage. The compression ratio is deterministic and independent of dataset, model, or method:
156
+
157
+ | Representation | Per-molecule (L=2048) | Per-molecule (L=4096) | 100K molecules (L=2048) |
158
+ |---|---|---|---|
159
+ | Raw binary FP (float64) | 16 KB | 32 KB | ~1.6 GB |
160
+ | Embedded, C=16 (float64) | 1 KB | 2 KB | ~100 MB |
161
+ | Embedded, C=32 (float64) | 512 B | 1 KB | ~50 MB |
162
+
163
+ A 16x reduction at C=16 applies unconditionally - it does not depend on the dataset, the ML model, or the compression method chosen. This matters for storing precomputed fingerprints on disk or in a database, transmitting embeddings over a network, loading datasets into memory for training, and caching repeated lookups via the built-in LRU cache.
164
+
165
+ ### ML Training and Inference Speed
166
+
167
+ The downstream ML model operates on the feature vector. Fewer features means faster training and prediction:
168
+
169
+ - **Tree-based models (Random Forest, XGBoost)**: Feature splitting cost is proportional to the number of features. Going from 2048 to 128 features means each tree split considers ~16x fewer candidates. For hyperparameter searches (e.g., Optuna with hundreds of trials), this compounds into significant wall-clock savings.
170
+ - **Neural networks**: The first dense layer's weight matrix shrinks from `(2048 x hidden)` to `(128 x hidden)` - 16x fewer parameters and 16x fewer multiply-adds per forward pass.
171
+ - **Distance-based methods (k-NN, similarity search)**: Pairwise distance computation is O(N² x D). Reducing D from 2048 to 128 gives a direct 16x speedup.
172
+
173
+ ### Memory During ML Training
174
+
175
+ During model training, the feature matrix for N=100K molecules occupies `(100000, 2048)` float64 = ~1.6 GB for raw fingerprints, versus `(100000, 128)` = ~100 MB for embedded. Tree-based models create internal copies and histograms proportional to feature count. GPU-based models benefit from smaller input tensors that allow larger batch sizes and better hardware utilization.
176
+
177
+ ### Compression Overhead
178
+
179
+ The compression step itself is negligible for block-wise methods (~1 ms per 1000 molecules). The total pipeline cost is:
180
+
181
+ - **Raw**: `skfp generation time`
182
+ - **Embedded**: `skfp generation time + ~1 ms per 1000 molecules` (block-wise)
183
+
184
+ The downstream ML speedup from 128 vs 2048 features far exceeds this overhead.
185
+
186
+ ### Sample Efficiency
187
+
188
+ High-dimensional spaces (2048 binary features) suffer from the curse of dimensionality - distances become less meaningful and models need exponentially more data to fill the space. Compressing to 128 dense, information-rich features acts as a form of regularization. Empirically, embedded fingerprints reach good predictive performance with fewer training samples than raw fingerprints. This is particularly valuable when labeled molecular data is scarce or expensive to obtain.
189
+
190
+ ### Summary of Advantages
191
+
192
+ | Metric | Raw FP (L=2048) | Embedded FP (D=128) | Advantage |
193
+ |---|-------------------------|---------------------------------|-----------------------|
194
+ | Feature matrix memory (100K mols) | ~1.6 GB | ~100 MB | 16x smaller |
195
+ | Per-molecule storage | 16 KB | 1 KB | 16x smaller |
196
+ | Tree model training speed | Baseline | ~16x fewer split candidates | Faster |
197
+ | Neural net first-layer params | 2048 x H | 128 x H | 16x fewer |
198
+ | Pairwise distance computation | O(N² x 2048) | O(N² x 128) | 16x faster |
199
+ | Small-dataset accuracy | Baseline | Often superior (regularization) | Better generalization |
200
+ | Large-dataset accuracy | Slightly higher ceiling | Comparable | Marginal tradeoff |
201
+
202
+ 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.
203
+
204
+ ## Project Structure
205
+
206
+ ```
207
+ fpembed/
208
+ ├── src/fpembed/ # pip-distributable package
209
+ │ ├── __init__.py
210
+ │ ├── generator.py # EmbeddedFingerprintGenerator
211
+ │ ├── compression.py # compress_fingerprint (orchestrator)
212
+ │ ├── compression_blockwise.py # block-wise weight schemes
213
+ │ ├── compression_projection.py # Hadamard SRHT + random projection
214
+ │ ├── smiles_utils.py # parse_smiles, canonicalize_smiles
215
+ │ ├── hashing.py # fp_params_hash
216
+ │ └── py.typed # PEP 561 marker
217
+ ├── examples/
218
+ │ ├── quickstart.ipynb # usage notebook
219
+ │ ├── datasets/ # molecular datasets (RedDB, NFA, QM9)
220
+ │ └── nicegui_app/ # NiceGUI demo application
221
+ ├── pyproject.toml
222
+ ├── environment.yml
223
+ └── README.md
224
+ ```
225
+
226
+ ## Installation
227
+
228
+ Install the core package (rdkit, numpy, selfies, scikit-fingerprints):
229
+
230
+ ```bash
231
+ pip install fpembed
232
+ ```
233
+
234
+ Install with demo app dependencies (nicegui, optuna, pandas, scikit-learn, etc.):
235
+
236
+ ```bash
237
+ pip install fpembed[app]
238
+ ```
239
+
240
+ For development (editable install):
241
+
242
+ ```bash
243
+ pip install -e .
244
+ ```
245
+
246
+ ### Conda Environment
247
+
248
+ A full conda environment is provided for reproducibility:
249
+
250
+ ```bash
251
+ conda env create -f environment.yml
252
+ conda activate fpembed
253
+ ```
254
+
255
+ This installs all dependencies and the `fpembed` package in editable mode.
256
+
257
+ ## Quick Start
258
+
259
+ ### Single Molecule (SMILES)
260
+
261
+ ```python
262
+ from fpembed import EmbeddedFingerprintGenerator
263
+
264
+ gen = EmbeddedFingerprintGenerator(
265
+ fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}
266
+ )
267
+
268
+ # Generate compressed fingerprint from SMILES
269
+ emb = gen.GetFingerprintFromSmiles("CCO")
270
+ print(emb.shape) # (128,)
271
+ ```
272
+
273
+ ### Different Fingerprint Types
274
+
275
+ ```python
276
+ # Atom Pair fingerprint
277
+ gen_ap = EmbeddedFingerprintGenerator(
278
+ fp_type="atom_pair", fp_size=2048, compression=16,
279
+ fp_params={"min_distance": 1, "max_distance": 30}
280
+ )
281
+
282
+ # Topological Torsion fingerprint
283
+ gen_tt = EmbeddedFingerprintGenerator(
284
+ fp_type="topological_torsion", fp_size=2048, compression=16,
285
+ fp_params={"torsion_atom_count": 4}
286
+ )
287
+ ```
288
+
289
+ ### Single Molecule (SELFIES)
290
+
291
+ ```python
292
+ emb = gen.GetFingerprintFromSelfies("[C][C][O]")
293
+ print(emb.shape) # (128,)
294
+ ```
295
+
296
+ ### Batch Processing
297
+
298
+ ```python
299
+ smiles_list = ["CCO", "c1ccccc1", "CC(=O)O", "invalid_smiles"]
300
+
301
+ embeddings, invalid_indices = gen.GetFingerprintsFromSmiles(smiles_list)
302
+ print(embeddings.shape) # (3, 128) - 3 valid molecules
303
+ print(invalid_indices) # [3] - index of invalid SMILES
304
+ ```
305
+
306
+ ### Raw Fingerprint (No Compression)
307
+
308
+ ```python
309
+ gen_raw = EmbeddedFingerprintGenerator(
310
+ fp_type="ecfp", fp_size=2048, compression=None, fp_params={"radius": 2}
311
+ )
312
+ fp = gen_raw.GetFingerprintFromSmiles("CCO")
313
+ print(fp.shape) # (2048,)
314
+ ```
315
+
316
+ ### Standalone Compression Function
317
+
318
+ ```python
319
+ import numpy as np
320
+ from fpembed import compress_fingerprint
321
+
322
+ fp = np.random.randint(0, 2, size=2048).astype(np.float64)
323
+ emb = compress_fingerprint(fp, size=16)
324
+ print(emb.shape) # (1, 128)
325
+ ```
326
+
327
+ ### Parameter Hashing
328
+
329
+ ```python
330
+ from fpembed import fp_params_hash
331
+
332
+ h = fp_params_hash("ecfp", {"radius": 2})
333
+ print(h) # 16-char hex string, stable across sessions
334
+ ```
335
+
336
+ ### Caching for Repeated Lookups
337
+
338
+ ```python
339
+ gen = EmbeddedFingerprintGenerator(
340
+ fp_type="ecfp", fp_size=2048, compression=16,
341
+ fp_params={"radius": 2}, cache_size=1024
342
+ )
343
+
344
+ # First call computes and caches
345
+ emb = gen.GetFingerprintFromSmiles("CCO")
346
+
347
+ # Second call returns cached result
348
+ emb2 = gen.GetFingerprintFromSmiles("CCO")
349
+
350
+ print(gen.cache_info()) # CacheInfo(hits=1, misses=1, maxsize=1024, currsize=1)
351
+ gen.clear_cache()
352
+ ```
353
+
354
+ ## Running the Demo App
355
+
356
+ The NiceGUI demo app provides an interactive UI for optimizing fingerprint embeddings. The examples are not included in the `pip install fpembed` package - clone the repository to access them.
357
+
358
+ **Warning**: the demo app uses a cache to speed up the calculations. Please provide at least 100 GB of free disk space before the evaluation. The obsolete cache file `examples/nicegui_app/cache.db` can be deleted manually afterward.
359
+
360
+ ```bash
361
+ git clone https://github.com/Sciencealone/fpembed.git
362
+ cd fpembed
363
+
364
+ # Install the core package with app dependencies
365
+ pip install fpembed[app]
366
+
367
+ # Or install pinned versions from requirements.txt
368
+ pip install -r requirements.txt
369
+
370
+ # Run the NiceGUI app
371
+ cd examples/nicegui_app
372
+ python app.py
373
+ ```
374
+
375
+ A Jupyter notebook with quick-start examples is also available at `examples/quickstart.ipynb`.
376
+
377
+ ## Datasets
378
+
379
+ The following datasets are included in `examples/datasets/` (obtained from their original sources):
380
+
381
+ | Dataset | DOI |
382
+ |---|---|
383
+ | RedDB Database | https://doi.org/10.1038/s41597-022-01832-2 |
384
+ | Non-Fullerene Acceptors Database | https://doi.org/10.1016/j.joule.2017.10.006 |
385
+ | QM9 Database | https://doi.org/10.1038/sdata.2014.22 |
386
+
387
+ ## License
388
+
389
+ This project is licensed under the terms of the MIT open source license. Please refer to the [LICENSE](LICENSE) for the full terms.
390
+
391
+ ## AI disclosure
392
+
393
+ AI usage during project development is declared in [aidecl.yaml](aidecl.yaml) following the [AI Declaration Format](https://ai-declaration.org/).
394
+
395
+ ## Support
396
+ This project is provided as-is, and may be updated over time. If you have questions, please open an issue.
@@ -4,15 +4,16 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fpembed"
7
- version = "0.1.0"
7
+ version = "0.1.2"
8
8
  description = "Generalized fingerprint embedding library"
9
9
  requires-python = ">=3.9"
10
10
  dependencies = [
11
- "rdkit==2026.3.1",
11
+ "rdkit==2025.9.3",
12
12
  "numpy==2.2.6",
13
13
  "selfies==1.0.2",
14
14
  "scikit-fingerprints==2.0.0",
15
15
  ]
16
+ readme = "README.md"
16
17
  license = "MIT"
17
18
  license-files = ["LICENSE*"]
18
19
 
@@ -0,0 +1,396 @@
1
+ Metadata-Version: 2.4
2
+ Name: fpembed
3
+ Version: 0.1.2
4
+ Summary: Generalized fingerprint embedding library
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: rdkit==2025.9.3
10
+ Requires-Dist: numpy==2.2.6
11
+ Requires-Dist: selfies==1.0.2
12
+ Requires-Dist: scikit-fingerprints==2.0.0
13
+ Provides-Extra: app
14
+ Requires-Dist: nicegui>=3.8.0; extra == "app"
15
+ Requires-Dist: optuna>=3.1.0; extra == "app"
16
+ Requires-Dist: pyyaml>=6.0; extra == "app"
17
+ Requires-Dist: altair>=5.0; extra == "app"
18
+ Requires-Dist: pandas>=1.5.2; extra == "app"
19
+ Requires-Dist: scikit-learn>=1.2.0; extra == "app"
20
+ Requires-Dist: tqdm>=4.64.1; extra == "app"
21
+ Dynamic: license-file
22
+
23
+ # FPembed - Generalized Molecular Fingerprint Embeddings
24
+
25
+ A lightweight Python package for generating compressed molecular fingerprint embeddings, backed by [scikit-fingerprints](https://github.com/scikit-fingerprints/scikit-fingerprints). Supports six binary fingerprint types through a single unified class.
26
+
27
+ 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.
28
+
29
+ The concept of compressing molecular fingerprints via weighted binary masking was originally introduced for Morgan fingerprints in the eMFP paper:
30
+
31
+ > Nuñez-Andrade, E. A., Vidal-Daza, I., Gomez-Bombarelli, R., Ryan, J. W., & Martin-Martinez, F. J. (2025).
32
+ > Embedded Morgan Fingerprints for more efficient molecular property predictions with machine learning.
33
+ > *ChemRxiv* (preprint). https://doi.org/10.26434/chemrxiv-2025-6hfp8
34
+
35
+ ```bibtex
36
+ @article{nunez2025emfp,
37
+ author = {Nu{\~n}ez-Andrade, Emilio A. and Vidal-Daza, Isaac and Gomez-Bombarelli, Rafael and Ryan, James W. and Martin-Martinez, Francisco J.},
38
+ title = {Embedded {Morgan} Fingerprints for more efficient molecular property predictions with machine learning},
39
+ journal = {ChemRxiv},
40
+ year = {2025},
41
+ doi = {10.26434/chemrxiv-2025-6hfp8},
42
+ note = {Preprint}
43
+ }
44
+ ```
45
+
46
+ Original concept repository: [MMLabCodes/eMFP](https://github.com/MMLabCodes/eMFP)
47
+
48
+ ## Supported Fingerprint Types
49
+
50
+ | Type | `fp_type` | Type-specific params |
51
+ |------|-----------|---------------------|
52
+ | Extended Connectivity (ECFP) | `ecfp` | `radius` (default 2) |
53
+ | Atom Pair | `atom_pair` | `min_distance` (1), `max_distance` (30) |
54
+ | Topological Torsion | `topological_torsion` | `torsion_atom_count` (4) |
55
+ | RDKit | `rdkit` | `min_path` (1), `max_path` (7) |
56
+ | Layered | `layered` | `min_path` (1), `max_path` (7) |
57
+ | Pattern | `pattern` | (none) |
58
+
59
+ ## Compression Methods
60
+
61
+ FPembed supports six compression methods, selectable via the `method` parameter on `EmbeddedFingerprintGenerator`. The default is `geometric`.
62
+
63
+ ### Method Reference
64
+
65
+ | Method (`method` value) | Category | `method_params` | Dynamic Range / Distance Preservation | Complexity |
66
+ |-------------------------|----------|-----------------|---------------------------------------|------------|
67
+ | `geometric` | block-wise | `interleave` (bool) | 65,536:1 dynamic range | O(L) |
68
+ | `linear` | block-wise | `interleave` (bool) | S:1 dynamic range | O(L) |
69
+ | `log` | block-wise | `interleave` (bool) | ~4.1:1 dynamic range | O(L) |
70
+ | `uniform` | block-wise | `interleave` (bool) | 1:1 (mean pooling) | O(L) |
71
+ | `hadamard` | global | `seed` (int) | orthogonal projection | O(L log L) |
72
+ | `random_projection` | global | `seed` (int) + `sparse` (bool) | JL distance preservation | O(L·D) |
73
+
74
+ ### The `method` Parameter
75
+
76
+ Pass `method` to the Generator constructor to select a compression strategy.
77
+
78
+ ### Method-Specific Parameters (`method_params`)
79
+
80
+ - **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.
81
+ - **`hadamard`**: accepts `seed` (int, default `42`). Controls the random sign flips applied before the Fast Walsh-Hadamard Transform.
82
+ - **`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.
83
+
84
+ Seed-based methods (`hadamard`, `random_projection`) are fully deterministic given the same seed and NumPy version. The default seed is `42`.
85
+
86
+ ### Code Examples
87
+
88
+ ```python
89
+ from fpembed import EmbeddedFingerprintGenerator
90
+
91
+ # Geometric (default)
92
+ gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2})
93
+
94
+ # Linear weights
95
+ gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}, method="linear")
96
+
97
+ # Logarithmic weights
98
+ gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}, method="log")
99
+
100
+ # Uniform weights (mean pooling)
101
+ gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}, method="uniform")
102
+
103
+ # Hadamard (SRHT)
104
+ gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}, method="hadamard")
105
+
106
+ # Random projection
107
+ gen = EmbeddedFingerprintGenerator(fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}, method="random_projection")
108
+ ```
109
+
110
+ Bit-interleaving with a block-wise method:
111
+
112
+ ```python
113
+ gen = EmbeddedFingerprintGenerator(
114
+ fp_type="ecfp", fp_size=2048, compression=16,
115
+ fp_params={"radius": 2}, method="linear",
116
+ method_params={"interleave": True}
117
+ )
118
+ ```
119
+
120
+ Standalone `compress_fingerprint` with a non-default method:
121
+
122
+ ```python
123
+ from fpembed import compress_fingerprint
124
+ import numpy as np
125
+
126
+ fp = np.random.randint(0, 2, size=2048).astype(np.float64)
127
+ emb = compress_fingerprint(fp, size=16, method="hadamard", method_params={"seed": 42})
128
+ print(emb.shape) # (1, 128)
129
+ ```
130
+
131
+ ### Choosing a Method
132
+
133
+ 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.
134
+
135
+ ### Performance Characteristics
136
+
137
+ All methods produce the same output dimensionality (D = L / compression) but differ in speed and memory:
138
+
139
+ | Method | Speed | Precomputed Memory | Best For |
140
+ |--------|------------------------------------------|--------------------------------------|----------|
141
+ | Block-wise (all four) | Fastest - single vectorized einsum, O(L) | Negligible (C-length weight vector) | Default choice; large batches |
142
+ | Random projection | Fast - BLAS matmul, O(L·D) | DxL matrix (~2 MB for L=2048, D=128) | Best theoretical guarantees (JL lemma) |
143
+ | Hadamard (SRHT) | Slowest - pure-Python FWHT, O(L log L) | L-length sign vector (~16 KB) | Small-scale experiments; future optimization |
144
+
145
+ Block-wise methods are ~2–5x faster than random projection and orders of magnitude faster than Hadamard in practice. Random projection's memory cost grows quadratically with fingerprint size.
146
+
147
+ ## Why Use Embedded Fingerprints?
148
+
149
+ Predictive accuracy is one axis of comparison between raw and embedded fingerprints - and the gap can be narrow, especially on large datasets where raw fingerprints have enough data to exploit all 2048 bits directly. However, accuracy is not the only metric that matters. Embedded fingerprints offer substantial, guaranteed advantages on every operational dimension: storage, speed, memory, and sample efficiency.
150
+
151
+ The core argument is not "embedded fingerprints are always more accurate" but rather "embedded fingerprints achieve comparable accuracy at a fraction of the computational cost."
152
+
153
+ ### Storage Size
154
+
155
+ This is the most clear-cut advantage. The compression ratio is deterministic and independent of dataset, model, or method:
156
+
157
+ | Representation | Per-molecule (L=2048) | Per-molecule (L=4096) | 100K molecules (L=2048) |
158
+ |---|---|---|---|
159
+ | Raw binary FP (float64) | 16 KB | 32 KB | ~1.6 GB |
160
+ | Embedded, C=16 (float64) | 1 KB | 2 KB | ~100 MB |
161
+ | Embedded, C=32 (float64) | 512 B | 1 KB | ~50 MB |
162
+
163
+ A 16x reduction at C=16 applies unconditionally - it does not depend on the dataset, the ML model, or the compression method chosen. This matters for storing precomputed fingerprints on disk or in a database, transmitting embeddings over a network, loading datasets into memory for training, and caching repeated lookups via the built-in LRU cache.
164
+
165
+ ### ML Training and Inference Speed
166
+
167
+ The downstream ML model operates on the feature vector. Fewer features means faster training and prediction:
168
+
169
+ - **Tree-based models (Random Forest, XGBoost)**: Feature splitting cost is proportional to the number of features. Going from 2048 to 128 features means each tree split considers ~16x fewer candidates. For hyperparameter searches (e.g., Optuna with hundreds of trials), this compounds into significant wall-clock savings.
170
+ - **Neural networks**: The first dense layer's weight matrix shrinks from `(2048 x hidden)` to `(128 x hidden)` - 16x fewer parameters and 16x fewer multiply-adds per forward pass.
171
+ - **Distance-based methods (k-NN, similarity search)**: Pairwise distance computation is O(N² x D). Reducing D from 2048 to 128 gives a direct 16x speedup.
172
+
173
+ ### Memory During ML Training
174
+
175
+ During model training, the feature matrix for N=100K molecules occupies `(100000, 2048)` float64 = ~1.6 GB for raw fingerprints, versus `(100000, 128)` = ~100 MB for embedded. Tree-based models create internal copies and histograms proportional to feature count. GPU-based models benefit from smaller input tensors that allow larger batch sizes and better hardware utilization.
176
+
177
+ ### Compression Overhead
178
+
179
+ The compression step itself is negligible for block-wise methods (~1 ms per 1000 molecules). The total pipeline cost is:
180
+
181
+ - **Raw**: `skfp generation time`
182
+ - **Embedded**: `skfp generation time + ~1 ms per 1000 molecules` (block-wise)
183
+
184
+ The downstream ML speedup from 128 vs 2048 features far exceeds this overhead.
185
+
186
+ ### Sample Efficiency
187
+
188
+ High-dimensional spaces (2048 binary features) suffer from the curse of dimensionality - distances become less meaningful and models need exponentially more data to fill the space. Compressing to 128 dense, information-rich features acts as a form of regularization. Empirically, embedded fingerprints reach good predictive performance with fewer training samples than raw fingerprints. This is particularly valuable when labeled molecular data is scarce or expensive to obtain.
189
+
190
+ ### Summary of Advantages
191
+
192
+ | Metric | Raw FP (L=2048) | Embedded FP (D=128) | Advantage |
193
+ |---|-------------------------|---------------------------------|-----------------------|
194
+ | Feature matrix memory (100K mols) | ~1.6 GB | ~100 MB | 16x smaller |
195
+ | Per-molecule storage | 16 KB | 1 KB | 16x smaller |
196
+ | Tree model training speed | Baseline | ~16x fewer split candidates | Faster |
197
+ | Neural net first-layer params | 2048 x H | 128 x H | 16x fewer |
198
+ | Pairwise distance computation | O(N² x 2048) | O(N² x 128) | 16x faster |
199
+ | Small-dataset accuracy | Baseline | Often superior (regularization) | Better generalization |
200
+ | Large-dataset accuracy | Slightly higher ceiling | Comparable | Marginal tradeoff |
201
+
202
+ 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.
203
+
204
+ ## Project Structure
205
+
206
+ ```
207
+ fpembed/
208
+ ├── src/fpembed/ # pip-distributable package
209
+ │ ├── __init__.py
210
+ │ ├── generator.py # EmbeddedFingerprintGenerator
211
+ │ ├── compression.py # compress_fingerprint (orchestrator)
212
+ │ ├── compression_blockwise.py # block-wise weight schemes
213
+ │ ├── compression_projection.py # Hadamard SRHT + random projection
214
+ │ ├── smiles_utils.py # parse_smiles, canonicalize_smiles
215
+ │ ├── hashing.py # fp_params_hash
216
+ │ └── py.typed # PEP 561 marker
217
+ ├── examples/
218
+ │ ├── quickstart.ipynb # usage notebook
219
+ │ ├── datasets/ # molecular datasets (RedDB, NFA, QM9)
220
+ │ └── nicegui_app/ # NiceGUI demo application
221
+ ├── pyproject.toml
222
+ ├── environment.yml
223
+ └── README.md
224
+ ```
225
+
226
+ ## Installation
227
+
228
+ Install the core package (rdkit, numpy, selfies, scikit-fingerprints):
229
+
230
+ ```bash
231
+ pip install fpembed
232
+ ```
233
+
234
+ Install with demo app dependencies (nicegui, optuna, pandas, scikit-learn, etc.):
235
+
236
+ ```bash
237
+ pip install fpembed[app]
238
+ ```
239
+
240
+ For development (editable install):
241
+
242
+ ```bash
243
+ pip install -e .
244
+ ```
245
+
246
+ ### Conda Environment
247
+
248
+ A full conda environment is provided for reproducibility:
249
+
250
+ ```bash
251
+ conda env create -f environment.yml
252
+ conda activate fpembed
253
+ ```
254
+
255
+ This installs all dependencies and the `fpembed` package in editable mode.
256
+
257
+ ## Quick Start
258
+
259
+ ### Single Molecule (SMILES)
260
+
261
+ ```python
262
+ from fpembed import EmbeddedFingerprintGenerator
263
+
264
+ gen = EmbeddedFingerprintGenerator(
265
+ fp_type="ecfp", fp_size=2048, compression=16, fp_params={"radius": 2}
266
+ )
267
+
268
+ # Generate compressed fingerprint from SMILES
269
+ emb = gen.GetFingerprintFromSmiles("CCO")
270
+ print(emb.shape) # (128,)
271
+ ```
272
+
273
+ ### Different Fingerprint Types
274
+
275
+ ```python
276
+ # Atom Pair fingerprint
277
+ gen_ap = EmbeddedFingerprintGenerator(
278
+ fp_type="atom_pair", fp_size=2048, compression=16,
279
+ fp_params={"min_distance": 1, "max_distance": 30}
280
+ )
281
+
282
+ # Topological Torsion fingerprint
283
+ gen_tt = EmbeddedFingerprintGenerator(
284
+ fp_type="topological_torsion", fp_size=2048, compression=16,
285
+ fp_params={"torsion_atom_count": 4}
286
+ )
287
+ ```
288
+
289
+ ### Single Molecule (SELFIES)
290
+
291
+ ```python
292
+ emb = gen.GetFingerprintFromSelfies("[C][C][O]")
293
+ print(emb.shape) # (128,)
294
+ ```
295
+
296
+ ### Batch Processing
297
+
298
+ ```python
299
+ smiles_list = ["CCO", "c1ccccc1", "CC(=O)O", "invalid_smiles"]
300
+
301
+ embeddings, invalid_indices = gen.GetFingerprintsFromSmiles(smiles_list)
302
+ print(embeddings.shape) # (3, 128) - 3 valid molecules
303
+ print(invalid_indices) # [3] - index of invalid SMILES
304
+ ```
305
+
306
+ ### Raw Fingerprint (No Compression)
307
+
308
+ ```python
309
+ gen_raw = EmbeddedFingerprintGenerator(
310
+ fp_type="ecfp", fp_size=2048, compression=None, fp_params={"radius": 2}
311
+ )
312
+ fp = gen_raw.GetFingerprintFromSmiles("CCO")
313
+ print(fp.shape) # (2048,)
314
+ ```
315
+
316
+ ### Standalone Compression Function
317
+
318
+ ```python
319
+ import numpy as np
320
+ from fpembed import compress_fingerprint
321
+
322
+ fp = np.random.randint(0, 2, size=2048).astype(np.float64)
323
+ emb = compress_fingerprint(fp, size=16)
324
+ print(emb.shape) # (1, 128)
325
+ ```
326
+
327
+ ### Parameter Hashing
328
+
329
+ ```python
330
+ from fpembed import fp_params_hash
331
+
332
+ h = fp_params_hash("ecfp", {"radius": 2})
333
+ print(h) # 16-char hex string, stable across sessions
334
+ ```
335
+
336
+ ### Caching for Repeated Lookups
337
+
338
+ ```python
339
+ gen = EmbeddedFingerprintGenerator(
340
+ fp_type="ecfp", fp_size=2048, compression=16,
341
+ fp_params={"radius": 2}, cache_size=1024
342
+ )
343
+
344
+ # First call computes and caches
345
+ emb = gen.GetFingerprintFromSmiles("CCO")
346
+
347
+ # Second call returns cached result
348
+ emb2 = gen.GetFingerprintFromSmiles("CCO")
349
+
350
+ print(gen.cache_info()) # CacheInfo(hits=1, misses=1, maxsize=1024, currsize=1)
351
+ gen.clear_cache()
352
+ ```
353
+
354
+ ## Running the Demo App
355
+
356
+ The NiceGUI demo app provides an interactive UI for optimizing fingerprint embeddings. The examples are not included in the `pip install fpembed` package - clone the repository to access them.
357
+
358
+ **Warning**: the demo app uses a cache to speed up the calculations. Please provide at least 100 GB of free disk space before the evaluation. The obsolete cache file `examples/nicegui_app/cache.db` can be deleted manually afterward.
359
+
360
+ ```bash
361
+ git clone https://github.com/Sciencealone/fpembed.git
362
+ cd fpembed
363
+
364
+ # Install the core package with app dependencies
365
+ pip install fpembed[app]
366
+
367
+ # Or install pinned versions from requirements.txt
368
+ pip install -r requirements.txt
369
+
370
+ # Run the NiceGUI app
371
+ cd examples/nicegui_app
372
+ python app.py
373
+ ```
374
+
375
+ A Jupyter notebook with quick-start examples is also available at `examples/quickstart.ipynb`.
376
+
377
+ ## Datasets
378
+
379
+ The following datasets are included in `examples/datasets/` (obtained from their original sources):
380
+
381
+ | Dataset | DOI |
382
+ |---|---|
383
+ | RedDB Database | https://doi.org/10.1038/s41597-022-01832-2 |
384
+ | Non-Fullerene Acceptors Database | https://doi.org/10.1016/j.joule.2017.10.006 |
385
+ | QM9 Database | https://doi.org/10.1038/sdata.2014.22 |
386
+
387
+ ## License
388
+
389
+ This project is licensed under the terms of the MIT open source license. Please refer to the [LICENSE](LICENSE) for the full terms.
390
+
391
+ ## AI disclosure
392
+
393
+ AI usage during project development is declared in [aidecl.yaml](aidecl.yaml) following the [AI Declaration Format](https://ai-declaration.org/).
394
+
395
+ ## Support
396
+ This project is provided as-is, and may be updated over time. If you have questions, please open an issue.
@@ -1,4 +1,4 @@
1
- rdkit==2026.3.1
1
+ rdkit==2025.9.3
2
2
  numpy==2.2.6
3
3
  selfies==1.0.2
4
4
  scikit-fingerprints==2.0.0
fpembed-0.1.0/PKG-INFO DELETED
@@ -1,20 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: fpembed
3
- Version: 0.1.0
4
- Summary: Generalized fingerprint embedding library
5
- License-Expression: MIT
6
- Requires-Python: >=3.9
7
- License-File: LICENSE
8
- Requires-Dist: rdkit==2026.3.1
9
- Requires-Dist: numpy==2.2.6
10
- Requires-Dist: selfies==1.0.2
11
- Requires-Dist: scikit-fingerprints==2.0.0
12
- Provides-Extra: app
13
- Requires-Dist: nicegui>=3.8.0; extra == "app"
14
- Requires-Dist: optuna>=3.1.0; extra == "app"
15
- Requires-Dist: pyyaml>=6.0; extra == "app"
16
- Requires-Dist: altair>=5.0; extra == "app"
17
- Requires-Dist: pandas>=1.5.2; extra == "app"
18
- Requires-Dist: scikit-learn>=1.2.0; extra == "app"
19
- Requires-Dist: tqdm>=4.64.1; extra == "app"
20
- Dynamic: license-file
@@ -1,20 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: fpembed
3
- Version: 0.1.0
4
- Summary: Generalized fingerprint embedding library
5
- License-Expression: MIT
6
- Requires-Python: >=3.9
7
- License-File: LICENSE
8
- Requires-Dist: rdkit==2026.3.1
9
- Requires-Dist: numpy==2.2.6
10
- Requires-Dist: selfies==1.0.2
11
- Requires-Dist: scikit-fingerprints==2.0.0
12
- Provides-Extra: app
13
- Requires-Dist: nicegui>=3.8.0; extra == "app"
14
- Requires-Dist: optuna>=3.1.0; extra == "app"
15
- Requires-Dist: pyyaml>=6.0; extra == "app"
16
- Requires-Dist: altair>=5.0; extra == "app"
17
- Requires-Dist: pandas>=1.5.2; extra == "app"
18
- Requires-Dist: scikit-learn>=1.2.0; extra == "app"
19
- Requires-Dist: tqdm>=4.64.1; extra == "app"
20
- Dynamic: license-file
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