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.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

chem_tactics-1.2.0.tar.gz (3.6 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

chem_tactics-1.2.0-py3-none-any.whl (3.6 MB view details)

Uploaded Python 3

File details

Details for the file chem_tactics-1.2.0.tar.gz.

File metadata

  • Download URL: chem_tactics-1.2.0.tar.gz
  • Upload date:
  • Size: 3.6 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for chem_tactics-1.2.0.tar.gz
Algorithm Hash digest
SHA256 fd47ed682f8bd7643a73ce24cd8f483961a994a326f394109329d21c1ce1a590
MD5 dbd65e594ab71e8756cc897cd3c9f5e9
BLAKE2b-256 9ce6057f20ca7847ce4451ea4589e3ee6dd7b296162a219e966c56a2db9a1ed4

See more details on using hashes here.

File details

Details for the file chem_tactics-1.2.0-py3-none-any.whl.

File metadata

  • Download URL: chem_tactics-1.2.0-py3-none-any.whl
  • Upload date:
  • Size: 3.6 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for chem_tactics-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b618f9fe439b579ceb87291eb754eab80d10d151f869e48e2a8536ba9dbfd35b
MD5 0713b01813370a94329c582cedb899d4
BLAKE2b-256 a72f4e773138c20f8fc47f2a726b0ded302c7fd9041df6500c52d895f26c59b2

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

1.2.0 This release

2 files

1.1.0

2 files

1.0.1

2 files

1.0.0

2 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