Skip to content
cccskills
FreePublished on GitHub

aizynthfinder-retrosynthesis

AiZynthFinder retrosynthetic route planning (CASP) from AstraZeneca Molecular AI. Monte Carlo tree search guided by a template-based neural expansion policy recursively disconnects a target SMILES until precursors are found in a purchasable stock. Covers config.yml (v4 format), aizynthcli batch screening, the AiZynthFinder/AiZynthExpander Python API, one-step disconnections, custom stocks via smiles2stock, scorers, Retro*/breadth-first/DFPN search alternatives, and reading output.json.gz / trees.json. Use for synthesis route planning, synthesizability screening, and building-block/precursor search. For reaction barriers use neb-irc-activation-energy; for 2D reaction scheme drawing use rdkit-chemdraw-cdxml.

The English summary is being prepared. Showing the original description.

Run score Not tested

See how to install

Files included (1)

  • SKILL.md19.4 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

AiZynthFinder Retrosynthesis

Overview

AiZynthFinder performs computer-aided synthesis planning (CASP): a search algorithm — Monte Carlo tree search by default — recursively disconnects a target molecule into precursors, guided by a neural expansion policy that ranks known reaction templates. The search terminates when all precursors are found in a stock (a set of purchasable building blocks) or the maximum depth is reached. Output is a ranked set of reaction trees plus per-target statistics (is_solved, step count, precursors in/out of stock).

Version covered: 4.4.1 (Python 3.10–3.12). The v4 config format differs substantially from v2/v3 as described in the 2020 paper — never copy a config from an old blog post without translating it.

When to Use

  • Planning a synthesis route for a designed or purchased target molecule
  • Screening a compound library for synthesizability before committing to make-on-demand
  • Finding purchasable precursors or building blocks that lead to a scaffold
  • Ranking design ideas by route length and by how many precursors fall outside a catalogue
  • Enumerating the first retro step only — plausible disconnections without a full tree
  • Testing whether a specific bond can be made disconnection-aware (break_bonds) in a route
  • Comparing solve rate across two building-block catalogues for the same target set
  • Use torchdrug instead when training a retrosynthesis model rather than running route search
  • For forward reaction barriers and transition states use neb-irc-activation-energy; for drawing the resulting scheme use rdkit-chemdraw-cdxml

Prerequisites

  • Python packages: aizynthfinder (4.4.x), rdkit, pandas
  • Data requirements: a stock file (InChIKeys), a trained expansion policy (ONNX model + template CSV), optionally a filter policy
  • Environment: Python 3.10–3.12. Default runtime is onnxruntime; TensorFlow is not needed unless serving remote models or loading legacy .hdf5 Keras models.

Check before installing — aizynthcli, download_public_data, and smiles2stock ship with the package and may already be on PATH inside a pixi/conda env. Inside a pixi project, invoke them as pixi run aizynthcli ....

command -v aizynthcli || {
  conda create "python>=3.10,<3.13" -n aizynth-env -y
  conda activate aizynth-env
  python -m pip install "aizynthfinder[all]"
}

[all] adds molbloom (bloom-filter stocks), pymongo, route-distances (route clustering), scipy, and timeout-decorator. Drop it for a lighter install; add [tf] only for TF-serving or .hdf5 models.

Quick Start

from aizynthfinder.aizynthfinder import AiZynthFinder

finder = AiZynthFinder(configfile="config.yml")
finder.stock.select("zinc")
finder.expansion_policy.select("uspto")

finder.target_smiles = "Cc1cccc(c1N(CC(=O)Nc2ccc(cc2)c3ncon3)C(=O)C4CCS(=O)(=O)CC4)C"
finder.tree_search()
finder.build_routes()                    # required before touching finder.routes

stats = finder.extract_statistics()
print(f"solved={stats['is_solved']} steps={stats['number_of_steps']} "
      f"routes={stats['number_of_routes']} time={stats['search_time']:.1f}s")
finder.routes[0]["image"].save("route_top.png")

Workflow

Step 1: Get the Models and Stock

download_public_data fetches the public USPTO models and the ZINC stock subset (several hundred MB, from zenodo.org and figshare.com) and writes a ready-to-use config.yml.

# Skip if the folder already holds the models — this is a large download.
test -f my_folder/config.yml || download_public_data my_folder

ls my_folder
# uspto_model.onnx              uspto_templates.csv.gz
# uspto_ringbreaker_model.onnx  uspto_ringbreaker_templates.csv.gz
# uspto_filter_model.onnx       zinc_stock.hdf5
# config.yml

Step 2: Write or Adjust config.yml

The list short-cut means "template-based strategy, model first, templates second, defaults elsewhere". The same short-cut works for a single filter model path and a single stock file path.

# config.yml — minimal
expansion:
  uspto:
    - uspto_model.onnx
    - uspto_templates.csv.gz
stock:
  zinc: zinc_stock.hdf5
# config.yml — explicit form, the settings that matter in practice
search:
  algorithm: mcts
  algorithm_config:
    C: 1.4
    use_prior: True
    prune_cycles_in_search: True
    search_rewards: ["state score"]
  max_transforms: 6
  iteration_limit: 100
  time_limit: 120
  return_first: false
  exclude_target_from_stock: True
expansion:
  uspto:
    type: template-based
    model: uspto_model.onnx
    template: uspto_templates.csv.gz
    template_column: retro_template
    cutoff_cumulative: 0.995
    cutoff_number: 50
    use_rdchiral: True
filter:
  uspto:
    type: quick-filter
    model: uspto_filter_model.onnx
    filter_cutoff: 0.05
stock:
  zinc:
    type: inchiset
    path: zinc_stock.hdf5
post_processing:
  min_routes: 5
  max_routes: 25
  all_routes: False

Values can be pulled from the environment: iteration_limit: ${ITERATION_LIMIT}.

Step 3: Validate the Target SMILES

An unparseable target burns the whole time limit before failing. Check first.

from rdkit import Chem

smiles = "Cc1cccc(c1N(CC(=O)Nc2ccc(cc2)c3ncon3)C(=O)C4CCS(=O)(=O)CC4)C"
mol = Chem.MolFromSmiles(smiles)
assert mol is not None, f"invalid SMILES: {smiles}"
smiles = Chem.MolToSmiles(mol)           # canonicalize
print(f"{smiles}  heavy_atoms={mol.GetNumHeavyAtoms()}")

Step 4: Run the Tree Search

select() picks which loaded policies and stocks are active. AiZynthFinder also accepts configdict=<dict> instead of a file — the cleanest way to sweep parameters without writing YAML.

from aizynthfinder.aizynthfinder import AiZynthFinder

finder = AiZynthFinder(configfile="config.yml")
finder.stock.select("zinc")
finder.expansion_policy.select("uspto")
finder.filter_policy.select("uspto")     # optional; prunes implausible reactions

finder.target_smiles = smiles
search_time = finder.tree_search()
print(f"search finished in {search_time:.1f}s")

Step 5: Build Routes and Read Statistics

build_routes() extracts reaction trees from the search graph. Nothing in finder.routes exists until it is called.

finder.build_routes()
stats = finder.extract_statistics()

for key in ("is_solved", "number_of_steps", "number_of_routes",
            "number_of_precursors", "number_of_precursors_in_stock",
            "search_time", "first_solution_time"):
    print(f"{key:32s} {stats[key]}")

print("not in stock:", stats["precursors_not_in_stock"])

Step 6: Inspect and Render Routes

finder.routes is a RouteCollection. Show two or three distinct routes, not only the top-scored one.

routes = finder.routes
print(f"{len(routes)} routes, scores: {routes.scores}")

for i in range(min(3, len(routes))):
    tree = routes.reaction_trees[i]
    leafs = [m.smiles for m in tree.leafs()]
    print(f"route {i}: solved={tree.is_solved} "
          f"steps={len(list(tree.reactions()))} branched={tree.is_branched()}")
    print(f"  precursors: {leafs}")
    routes.images[i].save(f"route_{i:02d}.png")

routes.jsons[0]                          # JSON string for the top route

Step 7: Batch Screen with aizynthcli

For hundreds or thousands of targets, use the CLI rather than a Python loop — --nproc splits the input across processes.

# One SMILES per line in smiles.txt
aizynthcli --config config.yml --smiles smiles.txt \
           --policy uspto --stocks zinc \
           --nproc 8 --checkpoint checkpoint.json.gz \
           --output output.json.gz --log_to_file
import pandas as pd

data = pd.read_json("output.json.gz", orient="table")
print(f"solve rate: {data.is_solved.mean():.1%}  n={len(data)}")
print(data.loc[data.is_solved, "number_of_steps"].value_counts().sort_index())
print(data.loc[~data.is_solved, ["target", "precursors_not_in_stock"]].head())

Key Parameters

ParameterDefaultRange / OptionsEffect
search.time_limit12030–1800 (s)Wall-clock budget per target. Raise this first when nothing solves.
search.iteration_limit10050–1000MCTS iterations per target; whichever of time/iterations hits first ends the search.
search.max_transforms63–10Maximum tree depth (longest route). Deeper searches cost quadratically more.
search.return_firstFalseTrue/FalseStop at the first solved route — fast synthesizability yes/no, poor route quality.
search.exclude_target_from_stockTrueTrue/FalseKeep True or a purchasable target returns an empty route.
search.algorithm_config.C1.40.5–3.0UCB exploration/exploitation balance; higher explores more disconnections.
search.algorithm_config.search_rewards["state score"]any scorer namesScorers driving the search; pair with search_rewards_weights for multi-objective.
expansion.cutoff_number5010–100Templates applied per expansion. Widens branching and slows search — tune after the time limit.
expansion.cutoff_cumulative0.9950.95–0.999Cumulative policy probability retained before truncating the template list.
expansion.template_columnretro_templatecolumn nameMust match the template file; a mismatch yields silently empty expansions.
filter.filter_cutoff0.050.0–0.5Feasibility threshold; raising it prunes harder and can make targets unsolvable.
post_processing.max_routes255–100Routes extracted after the search; all_routes: True returns every solved route.

Key Concepts

The route score is not a quality score

The state score reflects the fraction of solved precursors and the route length. It was designed to guide the tree search and is largely indiscriminate about whether a route is chemically sensible. Solved routes score near 1.0, unsolved ones typically below 0.8. Never present top_score as a confidence or feasibility measure.

Solve rate is set by the stock and the template library, not the algorithm

The public ZINC subset is far smaller than commercial catalogues; in the original comparison, adding Enamine building blocks found routes for 10 more compounds out of 100. Swapping USPTO for a Reaxys-derived policy changed which compounds solved rather than uniformly improving them. Findability tracks synthetic complexity — an unsolved target means "not found under this stock, this policy, and this budget", not "unsynthesizable".

Reference performance from the paper (100 random ChEMBL compounds, single CPU + single GPU): 55 solved, mean search time 38.7 s, mean time to first solution 7.1 s, mean 2.4 steps and 2.7 precursors.

No conditions are predicted

Reagents, solvents, temperatures, and yields are outside scope. A predicted route is a hypothesis for a chemist to evaluate.

Scorers

Loaded automatically: state score, number of reactions, number of pre-cursors, number of pre-cursors in stock. Also available in aizynthfinder.context.scoring: average template occurrence, sum of prices, route cost, max transform, broken bonds, fraction in stock, fraction in source, fraction of intermediates in <stock>, stock availability, reaction class membership, reaction class-rank score, delta-SC score, route similarity, plus CombinedScorer and DeepSetScorer. Scorers are addressed by their string name both in search.algorithm_config.search_rewards and in post_processing.route_scorer (which falls back to search_rewards when unset).

Search algorithms

Set search.algorithm to a class path to replace MCTS: Retro* (aizynthfinder.search.retrostar.search_tree.SearchTree), breadth-first (aizynthfinder.search.breadth_first.search_tree.SearchTree), DFPN (aizynthfinder.search.dfpn.search_tree.SearchTree).

Choosing an entry point

User intentUse
One or a few molecules, wants routes and imagesPython API (AiZynthFinder)
Hundreds or thousands of moleculesaizynthcli with a SMILES file and --nproc
Interactive exploration by a chemistaizynthapp (Jupyter GUI)
Only the first retro stepAiZynthExpander — far cheaper than a full tree search

Common Recipes

Recipe: One-Step Disconnections Only

When to use: the user wants plausible first disconnections, not a full route to purchasable material.

import pandas as pd
from aizynthfinder.aizynthfinder import AiZynthExpander

expander = AiZynthExpander(configfile="config.yml")
expander.expansion_policy.select("uspto")
expander.filter_policy.select("uspto")   # annotates feasibility only; does not prune

reactions = expander.do_expansion(smiles)

reactants = [[m.smiles for m in tup[0].reactants[0]] for tup in reactions]
metadata = pd.DataFrame([rxn.metadata for tup in reactions for rxn in tup])
print(f"{len(reactions)} disconnections; metadata fields: {list(metadata.columns)}")
print(metadata.head())   # template info, policy probability, filter feasibility

Recipe: Build a Custom Stock from a Catalogue

When to use: the ZINC subset is not the catalogue you actually buy from. Stock files must hold pre-computed InChIKeys, not SMILES — smiles2stock does the conversion.

# one SMILES per line
smiles2stock --files enamine_bb.smi inhouse.smi --output my_stock.hdf5
smiles2stock --files enamine_bb.smi --output my_db --target mongo   # MongoDB target
stock:
  enamine:
    type: inchiset
    path: my_stock.hdf5
  stop_criteria:
    price: 10
    counts:
      C: 10

inchiset also reads a CSV with an inchi_key column or a plain single-column text file. For a rule-based stock, subclass StockQueryMixin and implement __contains__(self, mol) over a Molecule, then point stock: type: at the importable class path.

Recipe: Multiple Expansion Policies and Disconnection-Aware Search

When to use: ring-forming disconnections are being missed (add RingBreaker), or a specific bond must be broken.

expansion:
  uspto:
    - uspto_model.onnx
    - uspto_templates.csv.gz
  ringbreaker:
    - uspto_ringbreaker_model.onnx
    - uspto_ringbreaker_templates.csv.gz
  multi_expansion_strategy:
    type: aizynthfinder.context.policy.MultiExpansionStrategy
    expansion_strategies: [uspto, ringbreaker]
    additive_expansion: True
search:
  break_bonds: [[1, 2], [3, 4]]     # atom-index pairs in the target
  break_bonds_operator: and         # "and" = all must break, "or" = any
  algorithm_config:
    search_rewards: ["state score", "broken bonds"]

Select it with aizynthcli --policy multi_expansion_strategy .... Bond indices depend on the target's atom ordering — derive them from the canonical SMILES you actually pass in and confirm the atom map with the user.

Recipe: Re-rank Routes and Render from a Saved Batch Run

When to use: the batch already ran, and you want a different ranking or images without re-searching.

import pandas as pd
from aizynthfinder.analysis import RouteSelectionArguments
from aizynthfinder.reactiontree import ReactionTree

# Widen route extraction, then re-rank by a different scorer
finder.build_routes(RouteSelectionArguments(nmin=5, nmax=50, return_all=True))
finder.routes.compute_scores(finder.scorers["number of reactions"])
finder.routes.rescore(finder.scorers["number of pre-cursors in stock"])

# Render routes stored in a batch output file
data = pd.read_json("output.json.gz", orient="table")
for i, tree in enumerate(data.trees.values[0]):
    ReactionTree.from_dict(tree).to_image().save(f"target0_route{i:03d}.png")

Expected Outputs

  • output.json.gz — batch results, one row per target; read with pd.read_json(..., orient="table"). Columns: target, search_time, first_solution_time, first_solution_iteration, number_of_nodes, max_transforms, max_children, number_of_routes, number_of_solved_routes, top_score, is_solved, number_of_steps, number_of_precursors, number_of_precursors_in_stock, precursors_in_stock, precursors_not_in_stock, precursors_availability, policy_used_counts, profiling, stock_info, top_scores, trees
  • trees.json — route trees for a single-SMILES CLI run (statistics go to the terminal)
  • checkpoint.json.gz — processed targets, so a crashed batch resumes; cat_aizynth_output concatenates several output files
  • route_*.png — rendered reaction trees from RouteCollection.images or ReactionTree.to_image()

Report is_solved, step count, and the precursors that fell outside stock. For a library screen report solve rate and the step-count distribution.

Troubleshooting

ProblemCauseSolution
PolicyException: number of templates does not agree with the output dimensions of the modelModel and template file come from different releasesRe-pair them; the ringbreaker model needs ringbreaker templates
Templates load but every expansion is emptyWrong template_columnDefault is retro_template. CSV templates are read with sep="\t", index_col=0 — a comma-separated file parses silently wrong
Nothing is ever in stockStock file holds SMILES, not InChIKeysRebuild with smiles2stock --files x.smi --output stock.hdf5
Target reported solved immediately with an empty routeThe target itself is in stockSet search.exclude_target_from_stock: True
Every search hits the time limit unsolvedBudget or branching too tight/wideRaise time_limit/iteration_limit first; then lower cutoff_number or max_transforms; return_first: True if any solution suffices
Config with top-level policy: / properties: keys failsPre-v4 formatTranslate to expansion: / filter: / search:
--nproc produces fewer output files than expectedOne shard failed and aborted concatenationCheck the per-process aizynthcli*.log files
finder.routes is empty or raisesbuild_routes() was not calledAlways call build_routes() after tree_search()
Clustering or distance_to unavailableroute-distances missingInstall the [all] extra
ImportError on TensorFlowAssuming the TF backendDefault runtime is onnxruntime; only use_remote_models or .hdf5 Keras models need [tf]

References

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

API + Python SDK for ordering cell-free protein expression and binding assays. Submit sequences for expression (10–100 µg), measure binding affinity (KD) against targets, track status, and retrieve results programmatically — no wet-lab setup. Built for ML-guided directed evolution and antibody/nanobody optimization. Requires Adaptyv account and API key.

The English summary is being prepared. Showing the original description.

jaechang-hits/SciAgent-Skills382Updated Oct 22, 2026

aeon

Free

scikit-learn compatible Python toolkit for time series ML: classify, cluster, regress, segment, transform with 30+ algorithms (ROCKET, InceptionTime, KNN-DTW, HIVE-COTE, WEASEL). Handles panel, multivariate, and unequal-length series. Maintained successor to sktime. Alternatives: sktime (larger ecosystem), tslearn (fewer algorithms), catch22 (features only).

The English summary is being prepared. Showing the original description.

jaechang-hits/SciAgent-Skills382Updated Oct 22, 2026

Access AlphaFold DB's 200M+ predicted structures by UniProt ID. Download PDB/mmCIF, analyze pLDDT/PAE, bulk-fetch proteomes via Google Cloud. For experimental structures use PDB; for prediction use ColabFold or ESMFold.

The English summary is being prepared. Showing the original description.

jaechang-hits/SciAgent-Skills382Updated Oct 22, 2026

Annotated matrices for single-cell genomics. Stores X with obs/var metadata, layers, embeddings (obsm/varm), graphs (obsp/varp), uns. Use for .h5ad/.zarr I/O, concatenation, scverse integration. For analysis use scanpy; for probabilistic models use scvi-tools.

The English summary is being prepared. Showing the original description.

jaechang-hits/SciAgent-Skills382Updated Oct 22, 2026

GRN inference from expression via GRNBoost2 (gradient boosting) or GENIE3 (Random Forest). Load matrix, filter by TFs, infer TF-target-importance links, save network. Dask-parallelized to single-cell scale. Core SCENIC component.

The English summary is being prepared. Showing the original description.

jaechang-hits/SciAgent-Skills382Updated Oct 22, 2026

Query ARCHS4 REST API for uniformly processed RNA-seq expression, tissue patterns, co-expression across 1M+ human/mouse samples. Retrieve z-scores, co-expressed genes, samples by metadata, HDF5 matrices. For variant population genetics use gnomad-database; for pathway enrichment use gget-genomic-databases (Enrichr).

The English summary is being prepared. Showing the original description.

jaechang-hits/SciAgent-Skills382Updated Oct 22, 2026

See all skills by jaechang-hits

Report a problem with this skill