Skip to main content

TACTICS

TACTICS Logo

Find the best compounds in a combinatorial library without enumerating all of them.

TACTICS (Thompson Sampling-Assisted Chemical Targeting and Iterative Compound Selection) uses Thompson Sampling to search ultra-large combinatorial libraries, scoring only a small fraction of the products while still recovering most of the top hits. A typical run evaluates 1–2% of the library and recovers ~85–95% of the true top-100.

📖 Documentation · 🧪 Tutorials · 📋 Changelog


Is this for you?

TACTICS fits if you have:

  • A combinatorial library defined by a reaction plus lists of reagents (2 or 3 components), and
  • A scoring function that is too slow to run on every product — docking, shape similarity, or an ML model — or a big table of precomputed scores.

If your library is small enough to score exhaustively, you don't need TACTICS.

Install

pip install chem-tactics

Requires Python 3.11+.

Optional extras and development install
pip install chem-tactics[tutorials]   # interactive marimo notebooks
pip install chem-tactics[test]        # test dependencies

# development install
git clone https://github.com/aakankschit/TACTICS.git
cd TACTICS
pip install -e ".[test]"

Quickstart

Screen a two-component amide library against a table of precomputed scores:

from TACTICS.library_enumeration import SynthesisPipeline, ReactionConfig, ReactionDef
from TACTICS.thompson_sampling import ThompsonSampler, get_preset
from TACTICS.thompson_sampling.core.evaluator_config import LookupEvaluatorConfig

# 1. Describe the library: one reaction + one reagent file per component
pipeline = SynthesisPipeline(ReactionConfig(
    reactions=[ReactionDef(
        reaction_smarts="[C:1](=O)[OH].[NH2:2]>>[C:1](=O)[NH:2]",
        step_index=0,
    )],
    reagent_file_list=["acids.smi", "amines.smi"],
))

# 2. Describe how to score a product
evaluator = LookupEvaluatorConfig(ref_filename="scores.csv")

# 3. Take a tuned preset and run
config = get_preset(synthesis_pipeline=pipeline, evaluator_config=evaluator)
sampler = ThompsonSampler.from_config(config)

sampler.warm_up(num_warmup_trials=config.num_warmup_trials)
results = sampler.search(num_cycles=config.num_ts_iterations)
sampler.close()

print(results.sort("score", descending=True).head(10))

results is a Polars DataFrame of every product that was evaluated, with columns score, SMILES and Name.

With LookupEvaluator and DBEvaluator, TACTICS skips molecule generation entirely and scores by product code, so SMILES reads FAIL and products are identified by Name (the underscore-joined reagent names). Evaluators that need a molecule — docking, ROCS, fingerprints — populate SMILES.

Slow scoring functions: use more cores

Docking and ROCS take seconds per molecule, so evaluation dominates runtime. Set processes to the number of cores you have — each worker builds its own docking session, so OpenEye evaluators parallelize correctly:

from TACTICS.thompson_sampling.core.evaluator_config import FredEvaluatorConfig

config = get_preset(
    synthesis_pipeline=pipeline,
    evaluator_config=FredEvaluatorConfig(design_unit_file="receptor.oedu"),
    mode="minimize",   # docking scores: lower is better
    batch_size=100,
)
config.processes = 32   # or os.cpu_count()

sampler = ThompsonSampler.from_config(config)

Leave processes=1 for LookupEvaluator and DBEvaluator. Their lookups are faster than the cost of shipping work to a worker, so parallelism makes those runs slower.

On macOS/Windows, guard your entry point with if __name__ == "__main__": — the default spawn start method re-imports your script in every worker. On a cluster, make sure the .oedu receptor is readable from every node (automatic on NFS/GPFS).

What can I plug in?

Scoring functions (evaluator_config):

Evaluator Use it for Speed
LookupEvaluatorConfig A CSV/Parquet table of precomputed scores instant
DBEvaluatorConfig Scores in a SQLite database instant
FPEvaluatorConfig Fingerprint similarity to a reference ligand fast
ROCSEvaluatorConfig 3D shape/colour overlay (OpenEye) slow
FredEvaluatorConfig Docking into a receptor (OpenEye) slow
MLClassifierEvaluatorConfig A trained scikit-learn model varies

Presets (get_preset(name, ...)):

Preset What it does
recommended (default) Top-Two Thompson Sampling — best overall
recommended_rws Roulette wheel with criticality-aware thermal cycling
baseline Balanced warmup + greedy, for measuring what the search adds
legacy_rws Reproduces Zhao et al. 2025

Start with recommended. If you are benchmarking, run recommended and recommended_rws and take the better result — they favour different library structures.

Building a config by hand instead of using a preset

Presets are just ThompsonSamplingConfig objects, so you can construct one directly to control the strategy, warmup and search budget:

from TACTICS.thompson_sampling import ThompsonSamplingConfig, ThompsonSampler
from TACTICS.thompson_sampling.strategies.config import TopTwoConfig
from TACTICS.thompson_sampling.warmup.config import EnhancedWarmupConfig

config = ThompsonSamplingConfig(
    synthesis_pipeline=pipeline,
    evaluator_config=evaluator,
    strategy_config=TopTwoConfig(mode="maximize", beta=0.5),
    warmup_config=EnhancedWarmupConfig(),
    num_warmup_trials=3,
    num_ts_iterations=1000,
    batch_size=100,
    processes=1,
    seed=42,
)

sampler = ThompsonSampler.from_config(config)

Configs are Pydantic models, so invalid values raise ValidationError at construction rather than failing mid-run. Other selection strategies — GreedyConfig, RouletteWheelConfig, UCBConfig, EpsilonGreedyConfig, BayesUCBConfig — are available for baselines and comparisons.

See the configuration docs for the full reference.

Learn more

Interactive marimo notebooks in tutorials/ (they default to a bundled thrombin dataset, so they run with no setup):

marimo run tutorials/thompson_sampling_tutorial.py    # compare strategies
marimo edit tutorials/library_enumeration_tutorial.py # build a library
Tutorial What it covers
thompson_sampling_tutorial.py Comparing selection strategies
library_enumeration_tutorial.py SynthesisPipeline and enumeration
reaction_config_builder.py Building and validating reaction SMARTS
custom_evaluator_tester.py Writing your own evaluator
  • API reference and guides: TACTICS Documentation
  • Runnable scripts: see examples/
  • Build docs locally: cd docs && make html

Contributing

Fork, branch, add tests for new behaviour, make sure pytest tests/ passes, then open a pull request.

Citation

@software{tactics,
    title={TACTICS: Thompson Sampling-Assisted Chemical Targeting and
           Iterative Compound Selection for Drug Discovery},
    author={Aakankschit Nandkeolyar},
    year={2025},
    url={https://github.com/aakankschit/TACTICS}
}

Support

Open an issue on GitHub, or contact anandkeo@uci.edu.

Licensed under the MIT License — see LICENSE.


This work is based on previous work by Patrick Walters. This project is a collaboration between the University of California Irvine, Leiden University and Groningen University.

Metadata

Release files for chem-tactics 1.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for chem-tactics 1.2.0
File Size Uploaded
chem_tactics-1.2.0.tar.gz 3.6 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for chem-tactics 1.2.0
File Interpreter ABI Platform
chem_tactics-1.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 7.2 MB

Release files / chem_tactics-1.2.0.tar.gz

Download URL chem_tactics-1.2.0.tar.gz
Size 3.6 MB
Tags Source
SHA-256 checksum
How to use checksums
fd47ed682f8bd7643a73ce24cd8f483961a994a326f394109329d21c1ce1a590
BLAKE2b-256 checksum
How to use checksums
9ce6057f20ca7847ce4451ea4589e3ee6dd7b296162a219e966c56a2db9a1ed4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.15

Release files / chem_tactics-1.2.0-py3-none-any.whl

Download URL chem_tactics-1.2.0-py3-none-any.whl
Size 3.6 MB
Tags Python 3
SHA-256 checksum
How to use checksums
b618f9fe439b579ceb87291eb754eab80d10d151f869e48e2a8536ba9dbfd35b
BLAKE2b-256 checksum
How to use checksums
a72f4e773138c20f8fc47f2a726b0ded302c7fd9041df6500c52d895f26c59b2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.11.15

Release history Release notifications | RSS feed

This release

1.2.0 This release

2 release files

1.1.0

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page