3SAlgorythm
Bayesian optimization benchmark: is it worth diversifying the batch when picking which compounds to test in each round of a screen, instead of simply grabbing whatever the model scores highest?
Executive summary with plots, molecules, and findings: docs/FINDINGS.md (readable by anyone with access to this repo, no claude.ai login needed; there's also an interactive version, only visible if you're signed into the account that published it).
What's here
threeSalgorythm/ is an installable package with the reusable core; everything
else at the repo root is the benchmark harness and scripts that use it.
threeSalgorythm/objectives.py— objective functions:CompoundScreen(synthetic screen),Branin,Hartmann6, andRealCompoundScreen(real chemistry — any SMILES + continuous-property CSV, MoleculeNet-style; ECFP4 fingerprints via RDKit).threeSalgorythm/optimizers.py—RandomPicker,GPExpectedImprovementwith pluggable batch selection (TopKSelector,LocalPenalizationSelector,DPPSelector,KrigingBelieverSelector) and a Tanimoto kernel for real chemistry. Core package API — only needs numpy/scipy/scikit-learn.threeSalgorythm/recommend.py—3salgorythm-recommend, a CLI that runs the actual TopK/LocalPenalization/DPP comparison on your own SMILES+activity CSV and reports which one performed best on your data. Exists because Experiment 7 found no cheap, universal rule for which selector wins on a given library — so instead of guessing, this measures it directly, at a modest default budget. Installed automatically with the package.threeSalgorythm/optimizers_torch.py— BoTorch/GPyTorch optimizers (optional, needs thetorchextra):BoTorchQEI(off-the-shelfqLogExpectedImprovement, jointly optimized over the batch);TanimotoLocalPenalizedQEI(hybrid — BoTorch's fitted GP for the quality signal, LocalPenalization's quality-agnostic repulsion for diversity, repulsion computed from the GP's own Tanimoto kernel instead of an ad-hoc Lipschitz radius);SparseTanimotoLocalPenalizedGP(same mechanism on a stochastic variational GP — scales to training sets an exact GP can't afford).scalability_demo.py— exact vs. sparse GP fit time as the training set grows past what this project's real dataset can exercise (synthetic fingerprints, since BACE only has 1513 compounds).generalization_test.py— repeats the TopK/LocalPenalization/DPP comparison on real MoleculeNet regression datasets (drug-like and quantum-chemistry) to check whether the BACE finding generalizes. It mostly doesn't — seedocs/FINDINGS.md, Experiment 7.isolation_diagnostic.py— attempts to predict, from fingerprint geometry alone (no optimizer runs), which datasets LocalPenalization will win on. Negative result, confirmed by extending the dataset pool from 10 to 30 — the weak correlations at n=10 got weaker and partly flipped sign at n=30. Seedocs/FINDINGS.md.benchmark.py— command-line runner: sweeps optimizer × batch size × seed, saves results to JSON, and produces a comparison plot.test_benchmark.py— regression tests (pytest), also run on every push via GitHub Actions.ALGORITHMS.md/SPEC.md— the original spec and the mathematical detail of each selector.
Installation
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev,chem]"
# optional, only for threeSalgorythm/optimizers_torch.py:
pip install -e ".[torch]"
chem pulls in RDKit (needed for RealCompoundScreen); dev pulls in
pytest and matplotlib (needed for the benchmark/test scripts, not for the
threeSalgorythm package itself). Someone who only wants its core selectors
in their own project can just pip install 3SAlgorythm or pip install -e .
here with no extras — numpy/scipy/scikit-learn are the only hard
dependencies. The PyPI distribution is named 3SAlgorythm (matches this
repo); the importable package is threeSalgorythm — a leading digit isn't a
valid Python identifier, so pip install 3SAlgorythm then
import threeSalgorythm in your code.
The real-chemistry scenario uses MoleculeNet's BACE dataset:
mkdir -p data
curl -o data/bace.csv https://deepchemdata.s3-us-west-1.amazonaws.com/datasets/bace.csv
generalization_test.py (Experiment 7) needs 6 more MoleculeNet CSVs, same
source; --extra (the 20-dataset isolation-diagnostic follow-up) needs 2
more on top of those:
for f in delaney-processed.csv SAMPL.csv Lipophilicity.csv clearance.csv qm7.csv qm8.csv qm9.csv thermosol.csv hppb.csv; do
curl -o "data/$f" "https://deepchemdata.s3-us-west-1.amazonaws.com/datasets/$f"
done
Usage
pytest test_benchmark.py -q
python benchmark.py --quick # quick sweep, synthetic
python benchmark.py # full sweep (10 seeds, synthetic)
python benchmark.py --dataset real --seeds 0 1 2 3 4 # real data, PCA+Matern kernel
python benchmark.py --dataset real --optimizers GP-EI+LocalPenalization+Tanimoto \
--seeds 0 1 2 3 4 --batch-sizes 25 50 --lp-alpha 1.0 # the study's winner
--lp-alpha controls LocalPenalizationSelector's "risk appetite": 0 ignores
quality and prioritizes pure spatial novelty, 1 is the original behavior, higher
values are more conservative.
3salgorythm-recommend — which selector should you use?
This project's own science (Experiment 7, docs/FINDINGS.md) found no
reliable, cheap-to-compute rule for predicting whether
LocalPenalizationSelector will beat plain top-EI selection on a given
library — it depends on properties of the data that isolation/clustering
metrics didn't capture, even across 30 independent test datasets. So rather
than ship a rule, 3salgorythm-recommend runs the actual comparison on your
data:
pip install "3SAlgorythm[chem]"
3salgorythm-recommend my_screen.csv --smiles-col smiles --target-col activity
It needs a CSV with a SMILES column and a continuous property to maximize
(any MoleculeNet-style layout works). By default it simulates a 100-compound
budget in batches of 25, repeated over 6 random seeds — fast enough to run
interactively; --seeds, --batch-size, and --n-experiments scale it up
for a more reliable answer at the cost of runtime. It also flags a specific
failure mode this project ran into (Experiment 7's Clearance dataset): a
target capped at an assay ceiling, where many compounds tie for the maximum
and any strategy "wins" trivially.
Where this fits
A quick survey of what's already out there, and why this project isn't redundant with it:
- BoTorch/Ax (Meta) is the strongest general-purpose framework and what
threeSalgorythm/optimizers_torch.pyis built on. Its off-the-shelf batch acquisition (qLogExpectedImprovement) is joint-correct — it avoids picking near-duplicates by construction — but it's still quality-weighted, so it never bets on a genuinely low-predicted-improvement structural outlier. Confirmed twice in this project: with our own Tanimoto kernel, and independently with GAUCHE's (see below) — same result both times. - GAUCHE ships Tanimoto/fingerprint kernels for BoTorch out of the box —
essentially a packaged version of
threeSalgorythm.optimizers.TanimotoKernel/threeSalgorythm.optimizers_torch.TanimotoKernelTorch. It doesn't ship a quality-agnostic batch selector, so it inherits the same blind spot as plain BoTorch unless you add one. - Optuna, the most popular hyperparameter-tuning library, distributes
parallel workers close to independently — the same redundancy problem
TopKSelectorhas, largely unaddressed. - EDBO targets reaction optimization (catalyst × solvent × temperature), not library screening — a different problem shape than this project's.
- Google Vertex AI Vizier, AWS SageMaker Automatic Model Tuning — managed, black-box, built for ML hyperparameter tuning rather than experiment design with a domain-specific kernel.
- SigOpt (Intel) was discontinued as a managed service in September 2023;
its code lives on as the unmaintained
sigopt-server.
None of the above ships the mechanism this project's main finding depends on —
diversity that ignores predicted quality once something is spatially unique —
as a reusable component. TanimotoLocalPenalizedQEI in
threeSalgorythm/optimizers_torch.py is the attempt at closing that gap: BoTorch's
own fitted GP for the quality signal, LocalPenalization's repulsion for
diversity, computed from the GP's real Tanimoto kernel instead of a
separately-estimated Lipschitz constant.
Validated over 30 seeds on BACE: it finds the same structural outlier as the
hand-rolled sklearn selector in 27/30 (q=25) and 26/30 (q=50) seeds — against
0/30 for plain qLogExpectedImprovement and 0/30 for DPP — while running at
0.02–0.03s/round, faster than the sklearn version (0.2s) and ~250x faster than
off-the-shelf qLogExpectedImprovement (5–7s) at this scale.
Main finding
On real data (BACE, 1513 compounds), with a Tanimoto kernel instead of the usual
PCA projection, LocalPenalizationSelector finds the most potent compound in the
entire library across all 30 seeds tested, without exception — neither DPP, nor
KrigingBeliever, nor BoTorch's native acquisition function (qLogExpectedImprovement)
manage it. Independently confirmed with GAUCHE's own Tanimoto kernel implementation,
not just ours.
But it doesn't generalize. Repeating the same comparison on 9 more real
datasets (generalization_test.py) gives 2 clear wins, 2 clear losses, and 6
ties out of 10 total — no domain pattern, roughly a coin flip. The BACE result
depends on a genuine structural outlier existing in the library; when there
isn't one, LocalPenalization's quality-agnostic exploration is a mild
liability, not a free win. This is not a safe default upgrade over TopK —
see docs/FINDINGS.md, Experiment 7, for the full breakdown and the honest
recommendation table.
Publishing (for maintainers)
Published to PyPI as 3SAlgorythm
(the distribution name in pyproject.toml; the importable package is
threeSalgorythm — see Installation above). .github/workflows/publish.yml
builds the sdist and wheel and publishes on every GitHub Release, via
PyPI Trusted Publishing (OIDC —
no API token stored in the repo), registered for jmiguelmangas/3SAlgorythm,
workflow publish.yml. Cutting a new GitHub Release with a bumped version
in pyproject.toml is the entire release process.
python -m build && twine check dist/* passes locally as of this package's
current state — the metadata and both artifacts are valid.
License
MIT.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file 3salgorythm-0.2.0.tar.gz.
File metadata
- Download URL: 3salgorythm-0.2.0.tar.gz
- Upload date:
- Size: 24.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
de29fbac986cbd093c5f42631198e4d4f1da09e21f2737399253366bda486d3e
|
|
| MD5 |
741aab4b7e326e439fc5bc3de4fcc1e8
|
|
| BLAKE2b-256 |
67f73803e373366c73220b539a0d5b7560e236a6c811ce41e0d660dcd28b4ae4
|
Provenance
The following attestation bundles were made for 3salgorythm-0.2.0.tar.gz:
Publisher:
publish.yml on jmiguelmangas/3SAlgorythm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
3salgorythm-0.2.0.tar.gz -
Subject digest:
de29fbac986cbd093c5f42631198e4d4f1da09e21f2737399253366bda486d3e - Sigstore transparency entry: 2468788429
- Sigstore integration time:
-
Permalink:
jmiguelmangas/3SAlgorythm@321540ad2fe089dabec7b08616be3168940d9564 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/jmiguelmangas
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@321540ad2fe089dabec7b08616be3168940d9564 -
Trigger Event:
release
-
Statement type:
File details
Details for the file 3salgorythm-0.2.0-py3-none-any.whl.
File metadata
- Download URL: 3salgorythm-0.2.0-py3-none-any.whl
- Upload date:
- Size: 28.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
06051e118b96ecb30e5ab8993de0b711ab536e3230f48a5a268b09a8e526668b
|
|
| MD5 |
9d01f0be64c6a691f2b8a4758690a84b
|
|
| BLAKE2b-256 |
e7d202a7f97f64b98cce49923767567312fab8ebd665e4f2f0a35bdcc4d346ca
|
Provenance
The following attestation bundles were made for 3salgorythm-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on jmiguelmangas/3SAlgorythm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
3salgorythm-0.2.0-py3-none-any.whl -
Subject digest:
06051e118b96ecb30e5ab8993de0b711ab536e3230f48a5a268b09a8e526668b - Sigstore transparency entry: 2468788445
- Sigstore integration time:
-
Permalink:
jmiguelmangas/3SAlgorythm@321540ad2fe089dabec7b08616be3168940d9564 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/jmiguelmangas
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@321540ad2fe089dabec7b08616be3168940d9564 -
Trigger Event:
release
-
Statement type: