Skip to content

Latest commit

 

History

183 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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 · 🧱 Guides: Library → Scoring → Search → Scale → Inspect → Visualise · 🧪 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[viz]         # matplotlib/altair plotting (TS_Benchmarks, diagnostic_plots)
pip install chem-tactics[tutorials]   # interactive marimo notebooks (includes viz)
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 the bundled thrombin library (130 acids × 3,844 amines) against its precomputed docking scores — this runs as-is after pip install chem-tactics:

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

data = files("TACTICS.data.thrombin")  # bundled example: 130 acids x 3844 amines

# 1. Describe the library: one reaction, one reagent file per component
pipeline = SynthesisPipeline(ReactionConfig(
    reactions=[ReactionDef(
        reaction_smarts="[#6:1](=[O:2])[OH].[#7X3;H1,H2;!$(N[!#6]);!$(N[#6]=[O]):3]"
                        ">>[#6:1](=[O:2])[#7:3]",
        step_index=0,
    )],
    reagent_file_list=[str(data / "acids.smi"), str(data / "coupled_aa_sub.smi")],
))

# 2. Describe how a product is scored (here: a precomputed docking table)
evaluator = LookupEvaluatorConfig(ref_filename=str(data / "product_scores.parquet"))

# 3. Take the tuned preset, run, and read the results
config = get_preset(
    synthesis_pipeline=pipeline,
    evaluator_config=evaluator,
    mode="minimize",        # docking scores: lower is better
    num_iterations=20,      # cycles; 1000+ for a real screen
    batch_size=50,          # compounds per cycle
)
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").head(5))

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
CustomEvaluatorConfig Any Python function Mol -> float yours
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

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 Search guide and the reference 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 Compare strategies and warmups; recovery charts
library_enumeration_tutorial.py SynthesisPipeline: single-step, multi-step, alternative SMARTS
reaction_config_builder.py Build and validate a ReactionConfig interactively
custom_evaluator_tester.py Paste a scoring function and run it
diagnostic_benchmark_plots.py Mechanism plots over the diagnostic benchmark output
interactive_sar_explorer.py Hover-to-structure SAR explorer (needs data/scores)
manuscript_plots_ROCS.py Manuscript figures, ROCS libraries
manuscript_plots_docking.py Manuscript figures, docking libraries
manuscript_sar_plots.py Manuscript figures, reagent score landscapes

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.

About

Contains files used in development of the Thompson Sampling algorithm used to find hit molecules in chemical combinatorial libraries.

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages