TACTICS#
Thompson Sampling over combinatorial libraries: define, score, search.
You have reagent files, a reaction, and a way to score a product. TACTICS finds the best products in a library of millions by scoring a few thousand — maintaining a posterior per reagent and choosing what to make next from what it has learned. Six building blocks, composed in order:
A reaction SMARTS and one reagent file per component →
SynthesisPipeline. Validate coverage; multi-step; deprotection.
A product in, a number out. A lookup table, your own Python function, fingerprints, docking, a model.
get_preset → from_config → warm_up → search. Presets, hand-built
configs, choosing a strategy and warmup.
Worker processes for slow scorers; pre-enumerated products; budgeting a run on a cluster.
What the search learned: per-cycle diagnostics, the posterior landscape, a SAR summary, analysis functions.
Recovery benchmarks (Altair) and mechanism plots (matplotlib).
pip install chem-tactics[viz].
Your own strategy, warmup or evaluator class.
Every class and function, generated from the code.
The mathematics: Thompson Sampling, CATS, TT-TS, Bayes-UCB.
Interactive marimo notebooks.
Ten minutes#
pip install chem-tactics
Then run the bundled thrombin library (130 acids × 3,844 amines) against its precomputed docking scores — this is also the README quickstart, and the test suite executes it:
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 scored — score,
SMILES, Name. From here, 1. Library is where you swap
in your own reagents and reaction, and 2. Scoring is where
you swap in your own scorer.
Install extras#
pip install "chem-tactics[viz]" # matplotlib + altair: Block 6
pip install "chem-tactics[tutorials]" # marimo + viz
pip install "chem-tactics[openeye]" # ROCS / FRED evaluators (licence required)
pip install -e ".[test,docs]" # development
Requires Python 3.11+. Core dependencies are RDKit, NumPy, SciPy, Polars, Pydantic, tqdm, sqlitedict and joblib; everything else is optional and imported lazily.