Commit ·
25f9bfc
0
Parent(s):
Initial commit
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +3 -0
- .gitignore +15 -0
- LICENSE.OpenMDW-1.1 +49 -0
- README.md +351 -0
- compose-cuda.yaml +71 -0
- compose.yaml +64 -0
- img/containers.png +3 -0
- img/forward_reactions.png +0 -0
- img/jupyter.png +0 -0
- img/notebook.png +3 -0
- img/openwebui.png +3 -0
- img/openwebui_connections.png +3 -0
- img/openwebui_integrations.png +0 -0
- img/openwebui_prompt.png +3 -0
- img/openwebui_tools.png +0 -0
- img/predict_product.svg +10 -0
- img/predict_retrosynthesis.svg +9 -0
- img/predict_retrosynthesis_tree.svg +15 -0
- img/retro_reactions.png +0 -0
- img/retro_tree_graph.png +3 -0
- img/retro_tree_text.png +3 -0
- jupyter/Dockerfile +31 -0
- jupyter/notebook.ipynb +1567 -0
- jupyter/requirements.txt +7 -0
- mcp/Dockerfile +11 -0
- mcp/requirements.txt +4 -0
- mcp/server.py +198 -0
- mcp/start.sh +12 -0
- model-training/README.md +186 -0
- model-training/pistachio/README.md +133 -0
- model-training/pistachio/step1_download.sh +5 -0
- model-training/pistachio/step2_get_reactions.py +107 -0
- model-training/pistachio/step3_clean_reactions.py +10 -0
- model-training/pistachio/step4_standardize_reactions_config.yaml +31 -0
- model-training/pistachio/step5_remove_arrow_annotations.sh +19 -0
- model-training/pistachio/step6_preprocess_reactions_config.yaml +35 -0
- model-training/pistachio/step7_convert_to_jsonl.py +46 -0
- model-training/pistachio/step8_generate_vocab.py +83 -0
- model-training/requirements.txt +7 -0
- model-training/tools/analyze_explicit_hydrogens.py +122 -0
- model-training/tools/analyze_tokens.py +106 -0
- model-training/tools/check_mol_from_smiles.py +10 -0
- model-training/tools/check_tanimoto.py +46 -0
- model-training/tools/run_fit.py +50 -0
- model-training/tools/run_lr_finder.py +98 -0
- model-training/tools/run_predict.py +48 -0
- model-training/tools/run_test.py +48 -0
- model-training/training/cli_main.py +20 -0
- model-training/training/predict.yaml +16 -0
- model-training/training/test.yaml +35 -0
.gitattributes
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Environment
|
| 2 |
+
env
|
| 3 |
+
.venv
|
| 4 |
+
|
| 5 |
+
# Jupyter
|
| 6 |
+
.ipynb_checkpoints
|
| 7 |
+
|
| 8 |
+
# PyCharm
|
| 9 |
+
.idea
|
| 10 |
+
|
| 11 |
+
# Python bytecode
|
| 12 |
+
__pycache__/
|
| 13 |
+
*.pyc
|
| 14 |
+
*.pyo
|
| 15 |
+
*.pyd
|
LICENSE.OpenMDW-1.1
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
OpenMDW License Agreement, version 1.1 (OpenMDW-1.1)
|
| 2 |
+
|
| 3 |
+
By exercising rights granted to you under this agreement, you accept and agree
|
| 4 |
+
to its terms.
|
| 5 |
+
|
| 6 |
+
As used in this agreement, "Model Materials" means the materials provided to
|
| 7 |
+
you under this agreement, consisting of: (1) one or more machine learning
|
| 8 |
+
models (including architecture and parameters); and (2) all related artifacts
|
| 9 |
+
(including associated data, documentation and software) that are provided to
|
| 10 |
+
you hereunder.
|
| 11 |
+
|
| 12 |
+
Subject to your compliance with this agreement, permission is hereby granted,
|
| 13 |
+
free of charge, to deal in the Model Materials without restriction, including
|
| 14 |
+
under all copyright, patent, database, and trade secret rights included or
|
| 15 |
+
embodied therein.
|
| 16 |
+
|
| 17 |
+
If you distribute any portion of the Model Materials, you shall retain in your
|
| 18 |
+
distribution (1) a copy of this agreement, and (2) all copyright notices and
|
| 19 |
+
other notices of origin included in the Model Materials that are applicable to
|
| 20 |
+
your distribution.
|
| 21 |
+
|
| 22 |
+
If you file, maintain, or voluntarily participate in a lawsuit against any
|
| 23 |
+
person or entity asserting that the Model Materials directly or indirectly
|
| 24 |
+
infringe any patent or copyright, then all rights and grants made to you
|
| 25 |
+
hereunder are terminated, unless that lawsuit was in response to a
|
| 26 |
+
corresponding lawsuit first brought against you.
|
| 27 |
+
|
| 28 |
+
This agreement does not impose any restrictions or obligations with respect to
|
| 29 |
+
any use, modification, or sharing of any outputs generated by using the Model
|
| 30 |
+
Materials.
|
| 31 |
+
|
| 32 |
+
THE MODEL MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
| 33 |
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 34 |
+
FITNESS FOR A PARTICULAR PURPOSE, TITLE, NONINFRINGEMENT, ACCURACY, OR THE
|
| 35 |
+
ABSENCE OF LATENT OR OTHER DEFECTS OR ERRORS, WHETHER OR NOT DISCOVERABLE, ALL
|
| 36 |
+
TO THE GREATEST EXTENT PERMISSIBLE UNDER APPLICABLE LAW.
|
| 37 |
+
|
| 38 |
+
YOU ARE SOLELY RESPONSIBLE FOR (1) CLEARING RIGHTS OF OTHER PERSONS THAT MAY
|
| 39 |
+
APPLY TO THE MODEL MATERIALS OR ANY USE THEREOF, INCLUDING WITHOUT LIMITATION
|
| 40 |
+
ANY PERSON'S COPYRIGHTS OR OTHER RIGHTS INCLUDED OR EMBODIED IN THE MODEL
|
| 41 |
+
MATERIALS; (2) OBTAINING ANY NECESSARY CONSENTS, PERMISSIONS OR OTHER RIGHTS
|
| 42 |
+
REQUIRED FOR ANY USE OF THE MODEL MATERIALS; OR (3) PERFORMING ANY DUE
|
| 43 |
+
DILIGENCE OR UNDERTAKING ANY OTHER INVESTIGATIONS INTO THE MODEL MATERIALS OR
|
| 44 |
+
ANYTHING INCORPORATED OR EMBODIED THEREIN.
|
| 45 |
+
|
| 46 |
+
IN NO EVENT SHALL THE PROVIDERS OF THE MODEL MATERIALS BE LIABLE FOR ANY CLAIM,
|
| 47 |
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
| 48 |
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MODEL MATERIALS, THE
|
| 49 |
+
USE THEREOF OR OTHER DEALINGS THEREIN.
|
README.md
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
language:
|
| 3 |
+
- en
|
| 4 |
+
tags:
|
| 5 |
+
- chemistry
|
| 6 |
+
- reaction-prediction
|
| 7 |
+
- retrosynthesis
|
| 8 |
+
license: openmdw-1.1
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
# RXN-Sandbox
|
| 12 |
+
|
| 13 |
+
A lightweight, local instance of RXN for running forward reaction and retrosynthesis predictions.
|
| 14 |
+
|
| 15 |
+
> [!NOTE]
|
| 16 |
+
> This repository is cross-listed on **[GitHub](https://github.com/rxn4chemistry/rxn-sandbox)** and **[Hugging Face](https://huggingface.co/rxn4chemistry/rxn-sandbox)**.
|
| 17 |
+
|
| 18 |
+
## Table of Contents
|
| 19 |
+
- [Overview](#overview)
|
| 20 |
+
- [Prerequisites](#prerequisites)
|
| 21 |
+
- [Quick Start](#quick-start)
|
| 22 |
+
- [Usage](#usage)
|
| 23 |
+
- [Jupyter Notebook](#jupyter-notebook)
|
| 24 |
+
- [MCP Integration with OpenWebUI](#mcp-integration-with-openwebui)
|
| 25 |
+
- [Python Scripts](#python-scripts)
|
| 26 |
+
- [Container Management](#container-management)
|
| 27 |
+
|
| 28 |
+
## Overview
|
| 29 |
+
|
| 30 |
+
This repository enables offline chemical reaction prediction using transformer models. The available tasks are RXN's most used prediction functionality: forward reaction, single-step retrosynthesis, and retrosynthesis tree generation. These tasks are performed locally via Jupyter notebook, via dedicated Python scripts, or via LLM (using OpenWeb UI and MCP). The transformer models were trained using 2025Q2 Pistachio data.
|
| 31 |
+
|
| 32 |
+
| Forward Reaction | Retrosynthesis (Single Step) | Retrosynthesis (Tree) |
|
| 33 |
+
|:---:|:---:|:---:|
|
| 34 |
+
| <br>Predict products from reactants | <br>Predict reactants for one step | <br>Generate multi-step routes |
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
## Prerequisites
|
| 38 |
+
|
| 39 |
+
**Software**:
|
| 40 |
+
|
| 41 |
+
- [Docker](https://docs.docker.com/get-docker/) and [Docker Compose](https://docs.docker.com/compose/install) OR
|
| 42 |
+
[Podman](https://podman.io/getting-started/installation) and [Podman Compose](https://podman-desktop.io/docs/compose/setting-up-compose)
|
| 43 |
+
- [Git LFS](https://git-lfs.com/)
|
| 44 |
+
|
| 45 |
+
> [!NOTE]
|
| 46 |
+
> If using Podman, replace `docker` commands with `podman` throughout this guide.
|
| 47 |
+
>
|
| 48 |
+
> This repository uses Git LFS for `.ckpt` model files. Install Git LFS, then run `git lfs install` once on your machine before cloning or pulling the repository.
|
| 49 |
+
|
| 50 |
+
**Hardware**:
|
| 51 |
+
|
| 52 |
+
- **8GB RAM** (16GB recommended for retrosynthesis tree predictions)
|
| 53 |
+
- **10GB free disk space** for container images and models
|
| 54 |
+
- **Supported platforms:** macOS, Linux, Windows
|
| 55 |
+
- **Supported architectures:** Intel/AMD (x86_64) and ARM64 (Apple Silicon)
|
| 56 |
+
- **NVIDIA GPU** (optional) — required only when using `compose-cuda.yaml`
|
| 57 |
+
|
| 58 |
+
## Quick Start
|
| 59 |
+
|
| 60 |
+
Two Compose files are provided:
|
| 61 |
+
|
| 62 |
+
| File | Description |
|
| 63 |
+
|------|-------------|
|
| 64 |
+
| [`compose.yaml`](compose.yaml) | Default — CPU-only inference |
|
| 65 |
+
| [`compose-cuda.yaml`](compose-cuda.yaml) | GPU-accelerated inference via NVIDIA CUDA (requires an NVIDIA GPU and the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html)) |
|
| 66 |
+
|
| 67 |
+
Clone this repository, and from the root directory run the following commands. Replace `compose.yaml` with `compose-cuda.yaml` in each command to enable GPU acceleration.
|
| 68 |
+
|
| 69 |
+
#### 1. Build the Docker Images
|
| 70 |
+
|
| 71 |
+
```bash
|
| 72 |
+
docker compose -f compose.yaml build
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
#### 2. Start the Services
|
| 76 |
+
|
| 77 |
+
```bash
|
| 78 |
+
docker compose -f compose.yaml up -d
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
#### 3. Verify Running Containers
|
| 82 |
+
|
| 83 |
+
You should see all containers running:
|
| 84 |
+
|
| 85 |
+

|
| 86 |
+
|
| 87 |
+
#### 4. Access the Interface(s)
|
| 88 |
+
|
| 89 |
+
- **Jupyter Notebook**: http://localhost:8888/
|
| 90 |
+
- **OpenWeb UI**: http://localhost:3000/
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
## Usage
|
| 95 |
+
|
| 96 |
+
<details id="jupyter-notebook">
|
| 97 |
+
|
| 98 |
+
<summary>Jupyter Notebook (click to expand)</summary>
|
| 99 |
+
|
| 100 |
+
### Jupyter Notebook
|
| 101 |
+
|
| 102 |
+
Access Jupyter at http://localhost:8888/ to use the interactive notebook environment.
|
| 103 |
+
|
| 104 |
+

|
| 105 |
+
|
| 106 |
+
Use the provided [notebook.ipynb](http://localhost:8888/notebooks/notebook.ipynb) to explore examples and interact with the models.
|
| 107 |
+
|
| 108 |
+

|
| 109 |
+
|
| 110 |
+
### Initial Setup
|
| 111 |
+
|
| 112 |
+
Run the **Celery setup and helper functions** section first to:
|
| 113 |
+
- Import required libraries
|
| 114 |
+
- Configure the Celery application
|
| 115 |
+
- Define helper functions for visualizing results
|
| 116 |
+
|
| 117 |
+
### Product Prediction
|
| 118 |
+
|
| 119 |
+
Two examples are provided for product prediction (batch and single reaction). Customize the reactants list:
|
| 120 |
+
|
| 121 |
+
```python
|
| 122 |
+
# Set up a list of reactants to make predictions
|
| 123 |
+
reactants_list = ["CCI.O=Cc1ccc([N+](=O)[O-])c(O)c1"]
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
Configure prediction parameters:
|
| 127 |
+
|
| 128 |
+
```python
|
| 129 |
+
# Setup task kwargs
|
| 130 |
+
kwargs = {
|
| 131 |
+
"topn": 3, # Number of results per reactant
|
| 132 |
+
"num_beams": 5, # Number of beams used for prediction. Must be >= topn
|
| 133 |
+
}
|
| 134 |
+
```
|
| 135 |
+
After running the prediction, the results will be displayed in a table:
|
| 136 |
+
|
| 137 |
+

|
| 138 |
+
|
| 139 |
+
### Retrosynthesis Prediction
|
| 140 |
+
|
| 141 |
+
Retrosynthesis predictions process one product at a time. Set the target product:
|
| 142 |
+
|
| 143 |
+
```python
|
| 144 |
+
# Choose product for retrosynthesis prediction
|
| 145 |
+
product = "C=CC(=C)C[Si](C)(C)C"
|
| 146 |
+
```
|
| 147 |
+
Configure retrosynthesis-specific parameters:
|
| 148 |
+
|
| 149 |
+
```python
|
| 150 |
+
# Setup task kwargs
|
| 151 |
+
kwargs = {
|
| 152 |
+
"topn": 10, # Number of results per reactant
|
| 153 |
+
"num_beams": 10, # Number of beams used for prediction. Must be >= topn
|
| 154 |
+
"fap": 0.6, # Forward likelihood acceptance probability (not length averaged)
|
| 155 |
+
"fld": 0.2, # Forward likelihood delta required between the top2 forward prediction results
|
| 156 |
+
}
|
| 157 |
+
```
|
| 158 |
+
|
| 159 |
+
Results are displayed in a similar table format:
|
| 160 |
+
|
| 161 |
+

|
| 162 |
+
|
| 163 |
+
### Retrosynthesis Tree Prediction
|
| 164 |
+
|
| 165 |
+
Start by selecting a target product SMILES:
|
| 166 |
+
|
| 167 |
+
```python
|
| 168 |
+
# Choose product for retrosynthesis tree prediction
|
| 169 |
+
product = "C1C(C[Si](C)(C)C)=CCC2C(=O)OC(=O)C12"
|
| 170 |
+
```
|
| 171 |
+
|
| 172 |
+
Configure prediction and tree-specific parameters:
|
| 173 |
+
|
| 174 |
+
```python
|
| 175 |
+
# Setup task kwargs
|
| 176 |
+
kwargs = {
|
| 177 |
+
"topn": 15, # Number of results per reactant
|
| 178 |
+
"num_beams": 15, # Number of beams used for prediction. Must be >= topn
|
| 179 |
+
"fap": 0.6, # Forward likelihood acceptance probability (not length averaged)
|
| 180 |
+
"fld": 0.2, # Forward likelihood delta required between the top2 forward prediction results
|
| 181 |
+
"max_depth": 4, # Max depth of the retrosynthesis tree
|
| 182 |
+
"beam_width": 6, # Max amount of nodes being expanded in each step
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
```
|
| 186 |
+
|
| 187 |
+
> **⚠️ Performance Note**: Retrosynthesis tree predictions are computationally intensive and may take significant time to complete.
|
| 188 |
+
|
| 189 |
+
### Result Visualization
|
| 190 |
+
|
| 191 |
+
Two visualization options are available:
|
| 192 |
+
|
| 193 |
+
**1. Text Representation** - Complete textual view of predicted routes and steps:
|
| 194 |
+
|
| 195 |
+

|
| 196 |
+
|
| 197 |
+
**2. Graph Representation** - Visual tree structure with molecule expansion paths. Use the selector to switch between different prediction results:
|
| 198 |
+
|
| 199 |
+

|
| 200 |
+
|
| 201 |
+
</details>
|
| 202 |
+
|
| 203 |
+
<details id="mcp-integration-with-openwebui">
|
| 204 |
+
<summary>LLM (click to expand)</summary>
|
| 205 |
+
|
| 206 |
+
### MCP Integration with OpenWebUI
|
| 207 |
+
|
| 208 |
+
Access OpenWeb UI at http://localhost:3000/ to interact with RXN models using natural language through AI assistants.
|
| 209 |
+
|
| 210 |
+

|
| 211 |
+
|
| 212 |
+
#### Setup MCP Server Connection
|
| 213 |
+
|
| 214 |
+
1. Navigate to **Settings → Integrations**
|
| 215 |
+
2. Under **Manage Tool Servers**, click the `+` icon
|
| 216 |
+
3. Set URL to `http://localhost:8000`
|
| 217 |
+
4. Click **Verify Connection** and **Save**
|
| 218 |
+
|
| 219 |
+
<img src="img/openwebui_integrations.png" alt="OpenWeb UI New Integration" width="450"/>
|
| 220 |
+
|
| 221 |
+
#### Configure External Models (Optional)
|
| 222 |
+
|
| 223 |
+
To use external AI models:
|
| 224 |
+
1. Go to **Admin Settings → Connections**
|
| 225 |
+
2. Add your API key for the desired model provider
|
| 226 |
+
|
| 227 |
+

|
| 228 |
+
|
| 229 |
+
#### Enable RXN Tools
|
| 230 |
+
|
| 231 |
+
1. Select your preferred AI model
|
| 232 |
+
2. Click the **Integrations** button below the prompt input
|
| 233 |
+
3. Select **Tools** and toggle on `rxn-mcp-server`
|
| 234 |
+
|
| 235 |
+
> **Note**: You must re-enable the tool when switching models.
|
| 236 |
+
|
| 237 |
+

|
| 238 |
+
|
| 239 |
+
#### Using Natural Language
|
| 240 |
+
|
| 241 |
+
Interact with RXN functions using conversational prompts:
|
| 242 |
+
|
| 243 |
+

|
| 244 |
+
|
| 245 |
+
</details>
|
| 246 |
+
|
| 247 |
+
<details id="python-scripts">
|
| 248 |
+
|
| 249 |
+
<summary>Python Scripts (click to expand)</summary>
|
| 250 |
+
|
| 251 |
+
### Running via Scripts
|
| 252 |
+
|
| 253 |
+
If you prefer a command-line workflow, you can run predictions directly from the provided Python scripts without using Jupyter or OpenWebUI. This method may be useful for
|
| 254 |
+
running analyses on remote machines (which often lack GUIs).
|
| 255 |
+
|
| 256 |
+
#### Open a Shell in the Worker Container
|
| 257 |
+
|
| 258 |
+
The scripts are executed inside the `worker` container, where the models and Celery configuration are already available:
|
| 259 |
+
|
| 260 |
+
```bash
|
| 261 |
+
docker exec -it rxn-worker-1 bash
|
| 262 |
+
```
|
| 263 |
+
|
| 264 |
+
#### Available Example Scripts
|
| 265 |
+
|
| 266 |
+
- `python scripts/predict_product.py` — run forward reaction prediction examples
|
| 267 |
+
- `python scripts/predict_retrosynthesis.py` — run single-step retrosynthesis examples
|
| 268 |
+
- `python scripts/predict_retrosynthesis_tree.py` — run retrosynthesis tree examples
|
| 269 |
+
- `python scripts/run_notebook_examples.py` — run the same examples shown in the notebook in sequence (this is effectively a combination of the three prior scripts)
|
| 270 |
+
|
| 271 |
+
#### Customize Inputs and Parameters
|
| 272 |
+
|
| 273 |
+
Each script is intended to be edited before execution. The `scripts/` directory is mounted into the worker container, so local changes are immediately available without rebuilding the image. Update the input SMILES and prediction parameters directly in the file:
|
| 274 |
+
|
| 275 |
+
- `reactants_list` for forward reaction prediction
|
| 276 |
+
- `product` for retrosynthesis and retrosynthesis tree prediction
|
| 277 |
+
- `topn`, `num_beams`, `fap`, `fld`, `max_depth`, and `beam_width` as needed
|
| 278 |
+
|
| 279 |
+
Then run the script you want:
|
| 280 |
+
|
| 281 |
+
```bash
|
| 282 |
+
python scripts/predict_product.py
|
| 283 |
+
```
|
| 284 |
+
|
| 285 |
+
#### Exit the Container
|
| 286 |
+
|
| 287 |
+
When you are finished, leave the container shell with:
|
| 288 |
+
|
| 289 |
+
```bash
|
| 290 |
+
exit
|
| 291 |
+
```
|
| 292 |
+
</details>
|
| 293 |
+
|
| 294 |
+
## Container Management
|
| 295 |
+
|
| 296 |
+
### Container Architecture
|
| 297 |
+
|
| 298 |
+
The system consists of six containers:
|
| 299 |
+
|
| 300 |
+
- **redis** - Results backend for Celery tasks
|
| 301 |
+
- **broker** - RabbitMQ message queue for task distribution
|
| 302 |
+
- **worker** - Celery worker running the transformer models
|
| 303 |
+
- **jupyter** - Interactive notebook environment
|
| 304 |
+
- **mcp** - Model Context Protocol server for LLM integration
|
| 305 |
+
- **openwebui** - Web interface for AI assistant interaction
|
| 306 |
+
|
| 307 |
+
### Useful Commands
|
| 308 |
+
|
| 309 |
+
```bash
|
| 310 |
+
# Stopping services
|
| 311 |
+
docker compose -f compose.yaml stop
|
| 312 |
+
|
| 313 |
+
# Restarting services
|
| 314 |
+
docker compose -f compose.yaml restart
|
| 315 |
+
|
| 316 |
+
# Viewing logs (all services)
|
| 317 |
+
docker compose -f compose.yaml logs -f
|
| 318 |
+
|
| 319 |
+
# Viewing logs (specific service)
|
| 320 |
+
docker compose -f compose.yaml logs -f worker
|
| 321 |
+
|
| 322 |
+
# Removing everything (containers, networks, and volumes)
|
| 323 |
+
docker compose -f compose.yaml down -v
|
| 324 |
+
```
|
| 325 |
+
|
| 326 |
+
> [!TIP]
|
| 327 |
+
> Replace `-f compose.yaml` with `-f compose-cuda.yaml` in any of the commands above to manage the GPU-accelerated stack instead.
|
| 328 |
+
|
| 329 |
+
### Performance Notes
|
| 330 |
+
|
| 331 |
+
- **Forward Predictions**: Fast (seconds)
|
| 332 |
+
- **Single-Step Retrosynthesis**: Moderate (seconds to minutes)
|
| 333 |
+
- **Tree Retrosynthesis**: Slow (minutes to hours depending on depth/width)
|
| 334 |
+
|
| 335 |
+
**Optimization Tips**:
|
| 336 |
+
|
| 337 |
+
- Start with smaller `topn` and `num_beams` values
|
| 338 |
+
- Limit `max_depth` to 3-4 for tree predictions
|
| 339 |
+
- Use `beam_width` of 5-10 for reasonable performance
|
| 340 |
+
- Allocate 16GB RAM for complex tree predictions
|
| 341 |
+
|
| 342 |
+
**Benchmarking**:
|
| 343 |
+
|
| 344 |
+
GPU acceleration provides significant speedup, especially for complex retrosynthesis tree predictions. These results are from the Python scripts in the `scripts/` folder. Analysis
|
| 345 |
+
times can vary substantially depending on the query molecule(s) and parameters.
|
| 346 |
+
|
| 347 |
+
| Script | T4 GPU (AWS g4dn.xlarge) | M1 Mac CPU | GPU Speedup |
|
| 348 |
+
|--------|---------------------|------------|-------------|
|
| 349 |
+
| `predict_product.py` | **1.7 s** | 3.4 s | 2.0x |
|
| 350 |
+
| `predict_retrosynthesis.py` | **2.5 s** | 8.5 s | 3.4x |
|
| 351 |
+
| `predict_retrosynthesis_tree.py` | **87 s (1.5 min)** | 763 s (12.7 min) | 8.8x |
|
compose-cuda.yaml
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: rxn
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
|
| 5 |
+
redis:
|
| 6 |
+
image: public.ecr.aws/docker/library/redis
|
| 7 |
+
command: redis-server --requirepass ubuntu
|
| 8 |
+
environment:
|
| 9 |
+
- REDIS_PASSWORD=ubuntu
|
| 10 |
+
|
| 11 |
+
broker:
|
| 12 |
+
image: public.ecr.aws/docker/library/rabbitmq:3.13-management
|
| 13 |
+
environment:
|
| 14 |
+
RABBITMQ_DEFAULT_PASS: ubuntu
|
| 15 |
+
RABBITMQ_DEFAULT_USER: ubuntu
|
| 16 |
+
|
| 17 |
+
worker:
|
| 18 |
+
build:
|
| 19 |
+
context: ./worker
|
| 20 |
+
volumes:
|
| 21 |
+
- ./models:/app/models
|
| 22 |
+
- ./vocab:/app/vocab
|
| 23 |
+
- ./scripts:/app/scripts
|
| 24 |
+
environment:
|
| 25 |
+
CELERY_QUEUE: product_prediction,retro_prediction
|
| 26 |
+
CELERY_BROKER_URL: amqp://ubuntu:ubuntu@broker:5672//
|
| 27 |
+
CELERY_RESULT_BACKEND: redis://:ubuntu@redis:6379/0
|
| 28 |
+
depends_on: [redis, broker]
|
| 29 |
+
deploy:
|
| 30 |
+
resources:
|
| 31 |
+
reservations:
|
| 32 |
+
devices:
|
| 33 |
+
- driver: nvidia
|
| 34 |
+
count: all
|
| 35 |
+
capabilities: [gpu]
|
| 36 |
+
|
| 37 |
+
jupyter:
|
| 38 |
+
build:
|
| 39 |
+
context: ./jupyter
|
| 40 |
+
working_dir: /rxn
|
| 41 |
+
volumes:
|
| 42 |
+
- ./jupyter:/rxn
|
| 43 |
+
environment:
|
| 44 |
+
CELERY_QUEUE: product_prediction,retro_prediction
|
| 45 |
+
CELERY_BROKER_URL: amqp://ubuntu:ubuntu@broker:5672//
|
| 46 |
+
CELERY_RESULT_BACKEND: redis://:ubuntu@redis:6379/0
|
| 47 |
+
ports:
|
| 48 |
+
- "8888:8888"
|
| 49 |
+
depends_on: [redis, broker, worker]
|
| 50 |
+
|
| 51 |
+
mcp:
|
| 52 |
+
build:
|
| 53 |
+
context: ./mcp
|
| 54 |
+
ports:
|
| 55 |
+
- "8000:8000"
|
| 56 |
+
environment:
|
| 57 |
+
CELERY_BROKER_URL: amqp://ubuntu:ubuntu@broker:5672//
|
| 58 |
+
CELERY_RESULT_BACKEND: redis://:ubuntu@redis:6379/0
|
| 59 |
+
depends_on: [redis, broker, worker]
|
| 60 |
+
|
| 61 |
+
openwebui:
|
| 62 |
+
image: ghcr.io/open-webui/open-webui
|
| 63 |
+
ports:
|
| 64 |
+
- "3000:8080"
|
| 65 |
+
environment:
|
| 66 |
+
WEBUI_AUTH: "False"
|
| 67 |
+
volumes:
|
| 68 |
+
- openwebui-data:/app/backend/data
|
| 69 |
+
|
| 70 |
+
volumes:
|
| 71 |
+
openwebui-data:
|
compose.yaml
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: rxn
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
|
| 5 |
+
redis:
|
| 6 |
+
image: public.ecr.aws/docker/library/redis
|
| 7 |
+
command: redis-server --requirepass ubuntu
|
| 8 |
+
environment:
|
| 9 |
+
- REDIS_PASSWORD=ubuntu
|
| 10 |
+
|
| 11 |
+
broker:
|
| 12 |
+
image: public.ecr.aws/docker/library/rabbitmq:3.13-management
|
| 13 |
+
environment:
|
| 14 |
+
RABBITMQ_DEFAULT_PASS: ubuntu
|
| 15 |
+
RABBITMQ_DEFAULT_USER: ubuntu
|
| 16 |
+
|
| 17 |
+
worker:
|
| 18 |
+
build:
|
| 19 |
+
context: ./worker
|
| 20 |
+
volumes:
|
| 21 |
+
- ./models:/app/models
|
| 22 |
+
- ./vocab:/app/vocab
|
| 23 |
+
- ./scripts:/app/scripts
|
| 24 |
+
environment:
|
| 25 |
+
CELERY_QUEUE: product_prediction,retro_prediction
|
| 26 |
+
CELERY_BROKER_URL: amqp://ubuntu:ubuntu@broker:5672//
|
| 27 |
+
CELERY_RESULT_BACKEND: redis://:ubuntu@redis:6379/0
|
| 28 |
+
depends_on: [redis, broker]
|
| 29 |
+
|
| 30 |
+
jupyter:
|
| 31 |
+
build:
|
| 32 |
+
context: ./jupyter
|
| 33 |
+
working_dir: /rxn
|
| 34 |
+
volumes:
|
| 35 |
+
- ./jupyter:/rxn
|
| 36 |
+
environment:
|
| 37 |
+
CELERY_QUEUE: product_prediction,retro_prediction
|
| 38 |
+
CELERY_BROKER_URL: amqp://ubuntu:ubuntu@broker:5672//
|
| 39 |
+
CELERY_RESULT_BACKEND: redis://:ubuntu@redis:6379/0
|
| 40 |
+
ports:
|
| 41 |
+
- "8888:8888"
|
| 42 |
+
depends_on: [redis, broker, worker]
|
| 43 |
+
|
| 44 |
+
mcp:
|
| 45 |
+
build:
|
| 46 |
+
context: ./mcp
|
| 47 |
+
ports:
|
| 48 |
+
- "8000:8000"
|
| 49 |
+
environment:
|
| 50 |
+
CELERY_BROKER_URL: amqp://ubuntu:ubuntu@broker:5672//
|
| 51 |
+
CELERY_RESULT_BACKEND: redis://:ubuntu@redis:6379/0
|
| 52 |
+
depends_on: [redis, broker, worker]
|
| 53 |
+
|
| 54 |
+
openwebui:
|
| 55 |
+
image: ghcr.io/open-webui/open-webui
|
| 56 |
+
ports:
|
| 57 |
+
- "3000:8080"
|
| 58 |
+
environment:
|
| 59 |
+
WEBUI_AUTH: "False"
|
| 60 |
+
volumes:
|
| 61 |
+
- openwebui-data:/app/backend/data
|
| 62 |
+
|
| 63 |
+
volumes:
|
| 64 |
+
openwebui-data:
|
img/containers.png
ADDED
|
Git LFS Details
|
img/forward_reactions.png
ADDED
|
img/jupyter.png
ADDED
|
img/notebook.png
ADDED
|
Git LFS Details
|
img/openwebui.png
ADDED
|
Git LFS Details
|
img/openwebui_connections.png
ADDED
|
Git LFS Details
|
img/openwebui_integrations.png
ADDED
|
img/openwebui_prompt.png
ADDED
|
Git LFS Details
|
img/openwebui_tools.png
ADDED
|
img/predict_product.svg
ADDED
|
|
img/predict_retrosynthesis.svg
ADDED
|
|
img/predict_retrosynthesis_tree.svg
ADDED
|
|
img/retro_reactions.png
ADDED
|
img/retro_tree_graph.png
ADDED
|
Git LFS Details
|
img/retro_tree_text.png
ADDED
|
Git LFS Details
|
jupyter/Dockerfile
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM public.ecr.aws/docker/library/python:3.13-slim
|
| 2 |
+
|
| 3 |
+
# Prevents interactive prompts during apt install
|
| 4 |
+
ENV DEBIAN_FRONTEND=noninteractive
|
| 5 |
+
|
| 6 |
+
# Install system dependencies required by RDKit and matplotlib
|
| 7 |
+
RUN apt-get update && apt-get install -y \
|
| 8 |
+
build-essential \
|
| 9 |
+
libxrender1 \
|
| 10 |
+
libxext6 \
|
| 11 |
+
libsm6 \
|
| 12 |
+
libglib2.0-0 \
|
| 13 |
+
libgl1 \
|
| 14 |
+
ca-certificates \
|
| 15 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 16 |
+
|
| 17 |
+
WORKDIR /rxn
|
| 18 |
+
|
| 19 |
+
# Copy and install Python dependencies
|
| 20 |
+
COPY requirements.txt .
|
| 21 |
+
|
| 22 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 23 |
+
|
| 24 |
+
CMD ["jupyter", "notebook", \
|
| 25 |
+
"--ip=0.0.0.0", \
|
| 26 |
+
"--port=8888", \
|
| 27 |
+
"--no-browser", \
|
| 28 |
+
"--ServerApp.allow_root=True", \
|
| 29 |
+
"--IdentityProvider.token=", \
|
| 30 |
+
"--ServerApp.password=", \
|
| 31 |
+
"--ServerApp.default_url=/notebook.ipynb"]
|
jupyter/notebook.ipynb
ADDED
|
@@ -0,0 +1,1567 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"id": "6964ba26-6c99-4150-b2d2-0e2b55072993",
|
| 6 |
+
"metadata": {},
|
| 7 |
+
"source": [
|
| 8 |
+
"# RXN Sandbox"
|
| 9 |
+
]
|
| 10 |
+
},
|
| 11 |
+
{
|
| 12 |
+
"cell_type": "markdown",
|
| 13 |
+
"id": "290b4d5f-2315-469f-afcd-9bffde994f1b",
|
| 14 |
+
"metadata": {},
|
| 15 |
+
"source": [
|
| 16 |
+
"### Celery setup and helper functions"
|
| 17 |
+
]
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"cell_type": "code",
|
| 21 |
+
"execution_count": 1,
|
| 22 |
+
"id": "831d0e08-3b3d-4348-9de4-5befa7a081a6",
|
| 23 |
+
"metadata": {},
|
| 24 |
+
"outputs": [],
|
| 25 |
+
"source": [
|
| 26 |
+
"# All necessary imports\n",
|
| 27 |
+
"import time\n",
|
| 28 |
+
"import base64\n",
|
| 29 |
+
"import pandas as pd\n",
|
| 30 |
+
"import ipywidgets as widgets\n",
|
| 31 |
+
"from io import BytesIO\n",
|
| 32 |
+
"from rdkit import Chem\n",
|
| 33 |
+
"from rdkit.Chem import Draw\n",
|
| 34 |
+
"from celery import Celery\n",
|
| 35 |
+
"from celery.result import AsyncResult\n",
|
| 36 |
+
"from typing import Optional, Dict, Any\n",
|
| 37 |
+
"from IPython.display import display, HTML, clear_output, Markdown\n",
|
| 38 |
+
"\n",
|
| 39 |
+
"\n",
|
| 40 |
+
"# Initialize Celery\n",
|
| 41 |
+
"celery_app = Celery()\n",
|
| 42 |
+
"#print(\"Broker:\", celery_app.conf.broker_url)\n",
|
| 43 |
+
"#print(\"Backend:\", celery_app.conf.result_backend)\n",
|
| 44 |
+
"\n",
|
| 45 |
+
"\n",
|
| 46 |
+
"# Wait for the celery result\n",
|
| 47 |
+
"def wait_for_result(app: Celery, task_id: str, timeout: float = 120.0, poll: float = 0.5) -> Dict[str, Any]:\n",
|
| 48 |
+
" \"\"\"\n",
|
| 49 |
+
" Poll for a result with a timeout. If task updates state with meta (e.g., PROGRESS),\n",
|
| 50 |
+
" we surface that along the way.\n",
|
| 51 |
+
" \"\"\"\n",
|
| 52 |
+
" res = AsyncResult(task_id, app=app)\n",
|
| 53 |
+
" t0 = time.time()\n",
|
| 54 |
+
" last_state = None\n",
|
| 55 |
+
"\n",
|
| 56 |
+
" while True:\n",
|
| 57 |
+
" state = res.state\n",
|
| 58 |
+
" if state != last_state:\n",
|
| 59 |
+
" print(f\"State: {state} | Info: {res.info}\")\n",
|
| 60 |
+
" last_state = state\n",
|
| 61 |
+
"\n",
|
| 62 |
+
" if res.ready():\n",
|
| 63 |
+
" # could be SUCCESS or FAILURE; .get() will raise on FAILURE\n",
|
| 64 |
+
" return res.get(propagate=False) # returns exception object if failed\n",
|
| 65 |
+
"\n",
|
| 66 |
+
" if time.time() - t0 > timeout:\n",
|
| 67 |
+
" raise TimeoutError(f\"Task {task_id} did not finish in {timeout} seconds.\")\n",
|
| 68 |
+
" time.sleep(poll)\n",
|
| 69 |
+
"\n",
|
| 70 |
+
"\n",
|
| 71 |
+
"# Build results dataframe\n",
|
| 72 |
+
"def build_results_dataframe(records):\n",
|
| 73 |
+
" \"\"\"\n",
|
| 74 |
+
" Convert Celery result(s) into a DataFrame.\n",
|
| 75 |
+
" Only keeps: smiles, predicted_smiles, confidence\n",
|
| 76 |
+
" \"\"\"\n",
|
| 77 |
+
" if records is None:\n",
|
| 78 |
+
" return pd.DataFrame()\n",
|
| 79 |
+
" if isinstance(records, dict):\n",
|
| 80 |
+
" records = [records]\n",
|
| 81 |
+
" if not isinstance(records, list):\n",
|
| 82 |
+
" return pd.DataFrame()\n",
|
| 83 |
+
"\n",
|
| 84 |
+
" df = pd.DataFrame(records)\n",
|
| 85 |
+
" \n",
|
| 86 |
+
" if {\"reactants\", \"product\"}.issubset(df.columns):\n",
|
| 87 |
+
" df.insert(\n",
|
| 88 |
+
" 0, # put as first column (or change to another position)\n",
|
| 89 |
+
" \"Predicted Reaction\",\n",
|
| 90 |
+
" df[\"reactants\"].fillna(\"\").astype(str) + \" → \" + df[\"product\"].fillna(\"\").astype(str)\n",
|
| 91 |
+
" )\n",
|
| 92 |
+
" # Optionally remove the original columns:\n",
|
| 93 |
+
" df = df.drop(columns=[\"reactants\", \"product\"])\n",
|
| 94 |
+
"\n",
|
| 95 |
+
" cols = []\n",
|
| 96 |
+
" for c in [\"Predicted Reaction\", \"confidence\"]:\n",
|
| 97 |
+
" if c in df.columns:\n",
|
| 98 |
+
" cols.append(c)\n",
|
| 99 |
+
"\n",
|
| 100 |
+
" return df[cols]\n",
|
| 101 |
+
"\n",
|
| 102 |
+
"\n",
|
| 103 |
+
"# Show results table\n",
|
| 104 |
+
"def style_results_table(df, caption=\"Reaction Predictions\", align=\"left\"):\n",
|
| 105 |
+
" \"\"\"\n",
|
| 106 |
+
" Style the table:\n",
|
| 107 |
+
" - Bigger, bold caption\n",
|
| 108 |
+
" - Confidence mapped red→yellow→green (0→1), fixed range vmin=0, vmax=1\n",
|
| 109 |
+
" - Removes index\n",
|
| 110 |
+
" \"\"\"\n",
|
| 111 |
+
"\n",
|
| 112 |
+
" clear_output(wait=True)\n",
|
| 113 |
+
" \n",
|
| 114 |
+
" if df.empty:\n",
|
| 115 |
+
" display(HTML(\"<p><em>No results to display.</em></p>\"))\n",
|
| 116 |
+
" return\n",
|
| 117 |
+
"\n",
|
| 118 |
+
" df = df.copy()\n",
|
| 119 |
+
"\n",
|
| 120 |
+
" # Build the Styler\n",
|
| 121 |
+
" styler = df.style\n",
|
| 122 |
+
"\n",
|
| 123 |
+
" # Confidence formatting + red→yellow→green gradient (low→high)\n",
|
| 124 |
+
" # Lock range to [0, 1] so colors are consistent even for partial data.\n",
|
| 125 |
+
" if \"confidence\" in df.columns:\n",
|
| 126 |
+
" try:\n",
|
| 127 |
+
" styler = styler.format({\"confidence\": \"{:.3f}\"}, escape=None)\n",
|
| 128 |
+
" except TypeError:\n",
|
| 129 |
+
" styler = styler.format({\"confidence\": \"{:.3f}\"}, escape=False)\n",
|
| 130 |
+
" styler = styler.background_gradient(\n",
|
| 131 |
+
" subset=[\"confidence\"],\n",
|
| 132 |
+
" cmap=\"RdYlGn\", # red (low) → yellow → green (high)\n",
|
| 133 |
+
" vmin=0.0,\n",
|
| 134 |
+
" vmax=1.0,\n",
|
| 135 |
+
" )\n",
|
| 136 |
+
" styler = styler.set_properties(\n",
|
| 137 |
+
" subset=[\"confidence\"], **{\"font-weight\": \"500\",})\n",
|
| 138 |
+
"\n",
|
| 139 |
+
" # SMILES styling: bold + monospace + wrapping\n",
|
| 140 |
+
" if \"Predicted Reaction\" in df.columns:\n",
|
| 141 |
+
" styler = styler.set_properties(\n",
|
| 142 |
+
" subset=[\"Predicted Reaction\"], \n",
|
| 143 |
+
" **{\n",
|
| 144 |
+
" \"background-color\": \"#fafafa\", \n",
|
| 145 |
+
" \"text-align\": align,\n",
|
| 146 |
+
" \"font-weight\": \"600\",\n",
|
| 147 |
+
" \"font-family\": \"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace\",\n",
|
| 148 |
+
" })\n",
|
| 149 |
+
"\n",
|
| 150 |
+
" # Table styles (caption, headers, borders)\n",
|
| 151 |
+
" styler = styler.set_table_styles(\n",
|
| 152 |
+
" [\n",
|
| 153 |
+
" {\n",
|
| 154 |
+
" \"selector\": \"caption\",\n",
|
| 155 |
+
" \"props\": [\n",
|
| 156 |
+
" (\"caption-side\", \"top\"),\n",
|
| 157 |
+
" (\"font-weight\", \"700\"),\n",
|
| 158 |
+
" (\"font-size\", \"1.2rem\"),\n",
|
| 159 |
+
" (\"margin-bottom\", \"10px\"),\n",
|
| 160 |
+
" ],\n",
|
| 161 |
+
" },\n",
|
| 162 |
+
" {\n",
|
| 163 |
+
" \"selector\": \"th.col_heading\",\n",
|
| 164 |
+
" \"props\": [\n",
|
| 165 |
+
" (\"font-weight\", \"700\"),\n",
|
| 166 |
+
" (\"padding\", \"8px\"),\n",
|
| 167 |
+
" (\"text-align\", \"center\"),\n",
|
| 168 |
+
" ],\n",
|
| 169 |
+
" },\n",
|
| 170 |
+
" {\n",
|
| 171 |
+
" \"selector\": \"td\",\n",
|
| 172 |
+
" \"props\": [\n",
|
| 173 |
+
" (\"padding\", \"8px\"),\n",
|
| 174 |
+
" (\"vertical-align\", \"center\"),\n",
|
| 175 |
+
" ],\n",
|
| 176 |
+
" },\n",
|
| 177 |
+
" {\n",
|
| 178 |
+
" \"selector\": \"table\",\n",
|
| 179 |
+
" \"props\": [\n",
|
| 180 |
+
" (\"border-collapse\", \"separate\"),\n",
|
| 181 |
+
" (\"border-spacing\", \"0px\"),\n",
|
| 182 |
+
" (\"width\", \"100%\"),\n",
|
| 183 |
+
" ],\n",
|
| 184 |
+
" },\n",
|
| 185 |
+
" ]\n",
|
| 186 |
+
" )\n",
|
| 187 |
+
"\n",
|
| 188 |
+
" # Render with inline HTML (formatters already handled escaping logic)\n",
|
| 189 |
+
" try:\n",
|
| 190 |
+
" styler = styler.hide(axis=\"index\")\n",
|
| 191 |
+
" except Exception:\n",
|
| 192 |
+
" styler = styler.hide_index()\n",
|
| 193 |
+
"\n",
|
| 194 |
+
" # Use modern API first; fall back for older pandas\n",
|
| 195 |
+
" try:\n",
|
| 196 |
+
" html = styler.set_caption(caption).to_html() # escape behavior controlled in format()\n",
|
| 197 |
+
" except TypeError:\n",
|
| 198 |
+
" html = styler.set_caption(caption).to_html(escape=False)\n",
|
| 199 |
+
"\n",
|
| 200 |
+
" display(HTML(html))\n",
|
| 201 |
+
"\n",
|
| 202 |
+
"\n",
|
| 203 |
+
"# Display a retrosynthesis tree textually\n",
|
| 204 |
+
"def display_route(route, idx):\n",
|
| 205 |
+
" display(Markdown(\n",
|
| 206 |
+
" f\"## Route {idx + 1}\\n\"\n",
|
| 207 |
+
" f\"- **Score:** `{route['score']:.3f}`\\n\"\n",
|
| 208 |
+
" f\"- **Steps:** `{route['steps']}`\"\n",
|
| 209 |
+
" ))\n",
|
| 210 |
+
"\n",
|
| 211 |
+
" open_nodes = route.get(\"open_nodes\", [])\n",
|
| 212 |
+
" if open_nodes:\n",
|
| 213 |
+
" display(Markdown(f\"**Remaining molecules:** `{', '.join(open_nodes)}`\"))\n",
|
| 214 |
+
"\n",
|
| 215 |
+
" for i, arc in enumerate(route.get(\"arcs\", []), start=1):\n",
|
| 216 |
+
" product = arc[\"product\"]\n",
|
| 217 |
+
" reactants = \".\".join(arc[\"reactants\"])\n",
|
| 218 |
+
"\n",
|
| 219 |
+
" display(Markdown(\n",
|
| 220 |
+
" f\"#### Step {i}\\n\"\n",
|
| 221 |
+
" f\"**Product:** `{product}` \\n\"\n",
|
| 222 |
+
" f\"**Reactants:** `{reactants}` \\n\"\n",
|
| 223 |
+
" f\"- Forward likelihood: `{arc['forward_likelihood']:.3f}` \\n\"\n",
|
| 224 |
+
" f\"- Arc score: `{arc['arc_score']:.3f}` \\n\"\n",
|
| 225 |
+
" f\"- Retro confidence: `{arc.get('confidence', 'n/a')}`\"\n",
|
| 226 |
+
" ))\n",
|
| 227 |
+
"\n",
|
| 228 |
+
"\n",
|
| 229 |
+
"\n",
|
| 230 |
+
"\n",
|
| 231 |
+
"# Convert a molecule to PNG for faster representation\n",
|
| 232 |
+
"def mol_png_base64(smiles, size=(250, 200)):\n",
|
| 233 |
+
" mol = Chem.MolFromSmiles(smiles)\n",
|
| 234 |
+
" if mol is None:\n",
|
| 235 |
+
" return \"\"\n",
|
| 236 |
+
"\n",
|
| 237 |
+
" img = Draw.MolToImage(mol, size=size)\n",
|
| 238 |
+
" buf = BytesIO()\n",
|
| 239 |
+
" img.save(buf, format=\"PNG\")\n",
|
| 240 |
+
" return base64.b64encode(buf.getvalue()).decode(\"utf-8\")\n",
|
| 241 |
+
"\n",
|
| 242 |
+
"\n",
|
| 243 |
+
"# Draw a row of reactants that make a retrosynthesis tree step\n",
|
| 244 |
+
"def draw_reactant_row_png(reactants, expanded_smiles=None):\n",
|
| 245 |
+
" imgs = [mol_png_base64(smi) for smi in reactants]\n",
|
| 246 |
+
"\n",
|
| 247 |
+
" html = \"\"\"\n",
|
| 248 |
+
" <div style=\"\n",
|
| 249 |
+
" display: flex;\n",
|
| 250 |
+
" flex-direction: row;\n",
|
| 251 |
+
" justify-content: flex-start;\n",
|
| 252 |
+
" align-items: flex-start;\n",
|
| 253 |
+
" gap: 20px;\n",
|
| 254 |
+
" overflow-x: auto;\n",
|
| 255 |
+
" padding: 12px 20px;\n",
|
| 256 |
+
" border: 1px solid #ddd;\n",
|
| 257 |
+
" margin-bottom: 16px;\n",
|
| 258 |
+
" box-sizing: border-box;\n",
|
| 259 |
+
" \">\n",
|
| 260 |
+
" \"\"\"\n",
|
| 261 |
+
"\n",
|
| 262 |
+
" for smi, img in zip(reactants, imgs):\n",
|
| 263 |
+
" is_expanded = (smi == expanded_smiles)\n",
|
| 264 |
+
"\n",
|
| 265 |
+
" border = \"2px solid #1f77b4\" if is_expanded else \"1px solid #ccc\"\n",
|
| 266 |
+
" label = \"<div style='font-weight:700; color:#1f77b4;'>expanded ↓</div>\" if is_expanded else \"\"\n",
|
| 267 |
+
"\n",
|
| 268 |
+
" html += f\"\"\"\n",
|
| 269 |
+
" <div style=\"\n",
|
| 270 |
+
" display: flex;\n",
|
| 271 |
+
" flex-direction: column;\n",
|
| 272 |
+
" align-items: center;\n",
|
| 273 |
+
" flex: 0 0 auto;\n",
|
| 274 |
+
" min-width: 260px;\n",
|
| 275 |
+
" padding: 6px;\n",
|
| 276 |
+
" border: {border};\n",
|
| 277 |
+
" border-radius: 6px;\n",
|
| 278 |
+
" \">\n",
|
| 279 |
+
" <img src=\"data:image/png;base64,{img}\" style=\"display:block;\"/>\n",
|
| 280 |
+
" <div style=\"\n",
|
| 281 |
+
" margin-top: 6px;\n",
|
| 282 |
+
" font-size: 14px;\n",
|
| 283 |
+
" font-weight: 700;\n",
|
| 284 |
+
" text-align: center;\n",
|
| 285 |
+
" word-break: break-all;\n",
|
| 286 |
+
" font-family: monospace;\n",
|
| 287 |
+
" \">\n",
|
| 288 |
+
" {smi}\n",
|
| 289 |
+
" </div>\n",
|
| 290 |
+
" {label}\n",
|
| 291 |
+
" </div>\n",
|
| 292 |
+
" \"\"\"\n",
|
| 293 |
+
"\n",
|
| 294 |
+
" html += \"</div>\"\n",
|
| 295 |
+
" display(HTML(html))\n",
|
| 296 |
+
"\n",
|
| 297 |
+
"\n",
|
| 298 |
+
"# Draw the full retrosynthesis tree\n",
|
| 299 |
+
"def draw_route_png(route):\n",
|
| 300 |
+
" display(Markdown(\"## Target molecule\"))\n",
|
| 301 |
+
" draw_reactant_row_png([route[\"arcs\"][0][\"product\"]])\n",
|
| 302 |
+
"\n",
|
| 303 |
+
" for i, arc in enumerate(route[\"arcs\"]):\n",
|
| 304 |
+
" display(Markdown(f\"## Step {i + 1} reactants\"))\n",
|
| 305 |
+
"\n",
|
| 306 |
+
" # Determine which reactant is further decomposed\n",
|
| 307 |
+
" expanded = None\n",
|
| 308 |
+
" if i + 1 < len(route[\"arcs\"]):\n",
|
| 309 |
+
" expanded = route[\"arcs\"][i + 1][\"product\"]\n",
|
| 310 |
+
"\n",
|
| 311 |
+
" draw_reactant_row_png(\n",
|
| 312 |
+
" arc[\"reactants\"],\n",
|
| 313 |
+
" expanded_smiles=expanded\n",
|
| 314 |
+
" )\n",
|
| 315 |
+
"\n",
|
| 316 |
+
"\n",
|
| 317 |
+
"# Builds a selector to display the full retrosynthesis tree for the selected route\n",
|
| 318 |
+
"def tree_route_selector(results):\n",
|
| 319 |
+
" options = [\n",
|
| 320 |
+
" (f\"Route {i+1} | score={r['score']:.3f}\", i)\n",
|
| 321 |
+
" for i, r in enumerate(results)\n",
|
| 322 |
+
" ]\n",
|
| 323 |
+
" \n",
|
| 324 |
+
" def show_route(idx):\n",
|
| 325 |
+
" draw_route_png(results[idx])\n",
|
| 326 |
+
" \n",
|
| 327 |
+
" dropdown = widgets.Dropdown(\n",
|
| 328 |
+
" options=options,\n",
|
| 329 |
+
" description=\"Route\",\n",
|
| 330 |
+
" )\n",
|
| 331 |
+
" \n",
|
| 332 |
+
" ui = widgets.interactive(show_route, idx=dropdown)\n",
|
| 333 |
+
" display(ui)"
|
| 334 |
+
]
|
| 335 |
+
},
|
| 336 |
+
{
|
| 337 |
+
"cell_type": "markdown",
|
| 338 |
+
"id": "66ebc007-7a61-4f2f-badb-ede0efcfbc47",
|
| 339 |
+
"metadata": {},
|
| 340 |
+
"source": [
|
| 341 |
+
"### Product prediction for a batch of reactions"
|
| 342 |
+
]
|
| 343 |
+
},
|
| 344 |
+
{
|
| 345 |
+
"cell_type": "code",
|
| 346 |
+
"execution_count": 5,
|
| 347 |
+
"id": "9708b32c-41d5-4796-a4a7-16e7a740a979",
|
| 348 |
+
"metadata": {},
|
| 349 |
+
"outputs": [
|
| 350 |
+
{
|
| 351 |
+
"name": "stdout",
|
| 352 |
+
"output_type": "stream",
|
| 353 |
+
"text": [
|
| 354 |
+
"Task sent. Assigned task_id: 31bccfad-38b8-4cc5-965f-08552ed376f6\n",
|
| 355 |
+
"State: PENDING | Info: None\n",
|
| 356 |
+
"State: STARTED | Info: None\n",
|
| 357 |
+
"State: SUCCESS | Info: {'result': [{'reactants': 'CCI.O=Cc1ccc([N+](=O)[O-])c(O)c1', 'product': 'CCOc1cc(C=O)ccc1[N+](=O)[O-]', 'confidence': 0.9987357632971271, 'smiles': 'CCI.O=Cc1ccc([N+](=O)[O-])c(O)c1>>CCOc1cc(C=O)ccc1[N+](=O)[O-]'}, {'reactants': 'CCOc1cc(O)c(C=O)cc1OCC.OCCCBr', 'product': 'CCOc1cc(C=O)c(OCCCO)cc1OCC', 'confidence': 0.992653825419336, 'smiles': 'CCOc1cc(O)c(C=O)cc1OCC.OCCCBr>>CCOc1cc(C=O)c(OCCCO)cc1OCC'}, {'reactants': 'C=CCc1cc(OCc2ccccc2)ccc1O.CCBr', 'product': 'C=CCc1cc(OCc2ccccc2)ccc1OCC', 'confidence': 0.9966198460612675, 'smiles': 'C=CCc1cc(OCc2ccccc2)ccc1O.CCBr>>C=CCc1cc(OCc2ccccc2)ccc1OCC'}], 'time': 3.18107008934021}\n"
|
| 358 |
+
]
|
| 359 |
+
}
|
| 360 |
+
],
|
| 361 |
+
"source": [
|
| 362 |
+
"# Set up a list of reactants to make predictions\n",
|
| 363 |
+
"reactants_list = [\"CCI.O=Cc1ccc([N+](=O)[O-])c(O)c1\", \"CCOc1cc(O)c(C=O)cc1OCC.OCCCBr\", \"C=CCc1cc(OCc2ccccc2)ccc1O.CCBr\"]\n",
|
| 364 |
+
"\n",
|
| 365 |
+
"# Setup task kwargs\n",
|
| 366 |
+
"kwargs = {\n",
|
| 367 |
+
" \"topn\": 1, # Number of results per reactant\n",
|
| 368 |
+
" \"num_beams\": 3, # Number of beams used for prediction. Must be >= topn\n",
|
| 369 |
+
" \"device\": None, # Device used for predicting, either \"cuda\" or \"cpu\", None defaults to cuda if available\n",
|
| 370 |
+
" \"ckpt_forward\": \"Pistachio2025Q2-Forward\", # Default forward model\n",
|
| 371 |
+
" \"vocab\": \"Pistachio2025Q2\", # Vocab for default forward model\n",
|
| 372 |
+
" # \"ckpt_forward_path\": \"models/forward/Pistachio2025Q2-Forward.ckpt\", # Can be used instead of ckpt_forward\n",
|
| 373 |
+
" # \"vocab_path\": \"vocab/Pistachio2025Q2.txt\", # Can be used instead of vocab\n",
|
| 374 |
+
"}\n",
|
| 375 |
+
"\n",
|
| 376 |
+
"# Send the product_prediction task with the reaction list and kwargs\n",
|
| 377 |
+
"task = celery_app.send_task(\n",
|
| 378 |
+
" \"tasks.product_prediction\",\n",
|
| 379 |
+
" [reactants_list],\n",
|
| 380 |
+
" kwargs=kwargs,\n",
|
| 381 |
+
" queue=\"product_prediction\",\n",
|
| 382 |
+
")\n",
|
| 383 |
+
"print(\"Task sent. Assigned task_id: {}\".format(task.id))\n",
|
| 384 |
+
"\n",
|
| 385 |
+
"# Use the task id to get the result. Increase timeout if needed.\n",
|
| 386 |
+
"response = wait_for_result(celery_app, task.id, timeout=180)"
|
| 387 |
+
]
|
| 388 |
+
},
|
| 389 |
+
{
|
| 390 |
+
"cell_type": "code",
|
| 391 |
+
"execution_count": 6,
|
| 392 |
+
"id": "df6007a1-1e6d-4b71-9991-8d6a21febdf0",
|
| 393 |
+
"metadata": {
|
| 394 |
+
"scrolled": true
|
| 395 |
+
},
|
| 396 |
+
"outputs": [
|
| 397 |
+
{
|
| 398 |
+
"data": {
|
| 399 |
+
"text/html": [
|
| 400 |
+
"<style type=\"text/css\">\n",
|
| 401 |
+
"#T_cf7d5 caption {\n",
|
| 402 |
+
" caption-side: top;\n",
|
| 403 |
+
" font-weight: 700;\n",
|
| 404 |
+
" font-size: 1.2rem;\n",
|
| 405 |
+
" margin-bottom: 10px;\n",
|
| 406 |
+
"}\n",
|
| 407 |
+
"#T_cf7d5 th.col_heading {\n",
|
| 408 |
+
" font-weight: 700;\n",
|
| 409 |
+
" padding: 8px;\n",
|
| 410 |
+
" text-align: center;\n",
|
| 411 |
+
"}\n",
|
| 412 |
+
"#T_cf7d5 td {\n",
|
| 413 |
+
" padding: 8px;\n",
|
| 414 |
+
" vertical-align: center;\n",
|
| 415 |
+
"}\n",
|
| 416 |
+
"#T_cf7d5 table {\n",
|
| 417 |
+
" border-collapse: separate;\n",
|
| 418 |
+
" border-spacing: 0px;\n",
|
| 419 |
+
" width: 100%;\n",
|
| 420 |
+
"}\n",
|
| 421 |
+
"#T_cf7d5_row0_col0, #T_cf7d5_row1_col0, #T_cf7d5_row2_col0 {\n",
|
| 422 |
+
" background-color: #fafafa;\n",
|
| 423 |
+
" text-align: center;\n",
|
| 424 |
+
" font-weight: 600;\n",
|
| 425 |
+
" font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;\n",
|
| 426 |
+
"}\n",
|
| 427 |
+
"#T_cf7d5_row0_col1, #T_cf7d5_row2_col1 {\n",
|
| 428 |
+
" background-color: #006837;\n",
|
| 429 |
+
" color: #f1f1f1;\n",
|
| 430 |
+
" font-weight: 500;\n",
|
| 431 |
+
"}\n",
|
| 432 |
+
"#T_cf7d5_row1_col1 {\n",
|
| 433 |
+
" background-color: #016a38;\n",
|
| 434 |
+
" color: #f1f1f1;\n",
|
| 435 |
+
" font-weight: 500;\n",
|
| 436 |
+
"}\n",
|
| 437 |
+
"</style>\n",
|
| 438 |
+
"<table id=\"T_cf7d5\">\n",
|
| 439 |
+
" <caption>Product Prediction Results</caption>\n",
|
| 440 |
+
" <thead>\n",
|
| 441 |
+
" <tr>\n",
|
| 442 |
+
" <th id=\"T_cf7d5_level0_col0\" class=\"col_heading level0 col0\" >Predicted Reaction</th>\n",
|
| 443 |
+
" <th id=\"T_cf7d5_level0_col1\" class=\"col_heading level0 col1\" >confidence</th>\n",
|
| 444 |
+
" </tr>\n",
|
| 445 |
+
" </thead>\n",
|
| 446 |
+
" <tbody>\n",
|
| 447 |
+
" <tr>\n",
|
| 448 |
+
" <td id=\"T_cf7d5_row0_col0\" class=\"data row0 col0\" >CCI.O=Cc1ccc([N+](=O)[O-])c(O)c1 → CCOc1cc(C=O)ccc1[N+](=O)[O-]</td>\n",
|
| 449 |
+
" <td id=\"T_cf7d5_row0_col1\" class=\"data row0 col1\" >0.999</td>\n",
|
| 450 |
+
" </tr>\n",
|
| 451 |
+
" <tr>\n",
|
| 452 |
+
" <td id=\"T_cf7d5_row1_col0\" class=\"data row1 col0\" >CCOc1cc(O)c(C=O)cc1OCC.OCCCBr → CCOc1cc(C=O)c(OCCCO)cc1OCC</td>\n",
|
| 453 |
+
" <td id=\"T_cf7d5_row1_col1\" class=\"data row1 col1\" >0.993</td>\n",
|
| 454 |
+
" </tr>\n",
|
| 455 |
+
" <tr>\n",
|
| 456 |
+
" <td id=\"T_cf7d5_row2_col0\" class=\"data row2 col0\" >C=CCc1cc(OCc2ccccc2)ccc1O.CCBr → C=CCc1cc(OCc2ccccc2)ccc1OCC</td>\n",
|
| 457 |
+
" <td id=\"T_cf7d5_row2_col1\" class=\"data row2 col1\" >0.997</td>\n",
|
| 458 |
+
" </tr>\n",
|
| 459 |
+
" </tbody>\n",
|
| 460 |
+
"</table>\n"
|
| 461 |
+
],
|
| 462 |
+
"text/plain": [
|
| 463 |
+
"<IPython.core.display.HTML object>"
|
| 464 |
+
]
|
| 465 |
+
},
|
| 466 |
+
"metadata": {},
|
| 467 |
+
"output_type": "display_data"
|
| 468 |
+
}
|
| 469 |
+
],
|
| 470 |
+
"source": [
|
| 471 |
+
"# Build a dataframe with the result in the response\n",
|
| 472 |
+
"df = build_results_dataframe(response[\"result\"])\n",
|
| 473 |
+
"\n",
|
| 474 |
+
"# Create and show the data in a table\n",
|
| 475 |
+
"style_results_table(df, caption=\"Product Prediction Results\", align=\"center\")"
|
| 476 |
+
]
|
| 477 |
+
},
|
| 478 |
+
{
|
| 479 |
+
"cell_type": "markdown",
|
| 480 |
+
"id": "71f84c47-07d2-405f-b824-86d95dc9ea2a",
|
| 481 |
+
"metadata": {},
|
| 482 |
+
"source": [
|
| 483 |
+
"### Product prediction for a single reaction - top 3 results"
|
| 484 |
+
]
|
| 485 |
+
},
|
| 486 |
+
{
|
| 487 |
+
"cell_type": "code",
|
| 488 |
+
"execution_count": 7,
|
| 489 |
+
"id": "8c8fb6b9-2a2b-42b6-9fb8-e5356377a7c4",
|
| 490 |
+
"metadata": {},
|
| 491 |
+
"outputs": [
|
| 492 |
+
{
|
| 493 |
+
"name": "stdout",
|
| 494 |
+
"output_type": "stream",
|
| 495 |
+
"text": [
|
| 496 |
+
"Task sent. Assigned task_id: 10bdf9f8-e49f-4c5f-8f11-5d03f3f75d83\n",
|
| 497 |
+
"State: PENDING | Info: None\n",
|
| 498 |
+
"State: STARTED | Info: None\n",
|
| 499 |
+
"State: SUCCESS | Info: {'result': [{'reactants': 'CCI.O=Cc1ccc([N+](=O)[O-])c(O)c1', 'product': 'CCOc1cc(C=O)ccc1[N+](=O)[O-]', 'confidence': 0.9987357632971271, 'smiles': 'CCI.O=Cc1ccc([N+](=O)[O-])c(O)c1>>CCOc1cc(C=O)ccc1[N+](=O)[O-]'}, {'reactants': 'CCI.O=Cc1ccc([N+](=O)[O-])c(O)c1', 'product': 'CCOc1cc(C=O)ccc1[N+](=O)[O-].CCOc1cc(C=O)ccc1[N+](=O)[O-]', 'confidence': 0.8466943868002893, 'smiles': 'CCI.O=Cc1ccc([N+](=O)[O-])c(O)c1>>CCOc1cc(C=O)ccc1[N+](=O)[O-].CCOc1cc(C=O)ccc1[N+](=O)[O-]'}, {'reactants': 'CCI.O=Cc1ccc([N+](=O)[O-])c(O)c1', 'product': 'CCOc1cc(C=O)ccc1[N+](=O)[O-].CCOc1cc(CO)ccc1[N+](=O)[O-]', 'confidence': 0.7820610951050019, 'smiles': 'CCI.O=Cc1ccc([N+](=O)[O-])c(O)c1>>CCOc1cc(C=O)ccc1[N+](=O)[O-].CCOc1cc(CO)ccc1[N+](=O)[O-]'}], 'time': 3.020906448364258}\n"
|
| 500 |
+
]
|
| 501 |
+
}
|
| 502 |
+
],
|
| 503 |
+
"source": [
|
| 504 |
+
"# Set up a list of reactants to make predictions\n",
|
| 505 |
+
"reactants_list = [\"CCI.O=Cc1ccc([N+](=O)[O-])c(O)c1\"]\n",
|
| 506 |
+
"\n",
|
| 507 |
+
"# Setup task kwargs\n",
|
| 508 |
+
"kwargs = {\n",
|
| 509 |
+
" \"topn\": 3, # Number of results per reactant\n",
|
| 510 |
+
" \"num_beams\": 5, # Number of beams used for prediction. Must be >= topn\n",
|
| 511 |
+
" \"device\": None, # Device used for predicting, either \"cuda\" or \"cpu\", None defaults to cuda if available\n",
|
| 512 |
+
" \"ckpt_forward\": \"Pistachio2025Q2-Forward\", # Default forward model\n",
|
| 513 |
+
" \"vocab\": \"Pistachio2025Q2\", # Vocab for default forward model\n",
|
| 514 |
+
" # \"ckpt_forward_path\": \"models/forward/Pistachio2025Q2-Forward.ckpt\", # Can be used instead of ckpt_forward\n",
|
| 515 |
+
" # \"vocab_path\": \"vocab/Pistachio2025Q2.txt\", # Can be used instead of vocab\n",
|
| 516 |
+
"}\n",
|
| 517 |
+
"\n",
|
| 518 |
+
"# Send the product_prediction task with the reaction list and kwargs\n",
|
| 519 |
+
"task = celery_app.send_task(\n",
|
| 520 |
+
" \"tasks.product_prediction\",\n",
|
| 521 |
+
" [reactants_list],\n",
|
| 522 |
+
" kwargs=kwargs,\n",
|
| 523 |
+
" queue=\"product_prediction\",\n",
|
| 524 |
+
")\n",
|
| 525 |
+
"print(\"Task sent. Assigned task_id: {}\".format(task.id))\n",
|
| 526 |
+
"\n",
|
| 527 |
+
"# Use the task id to get the result. Increase timeout if needed.\n",
|
| 528 |
+
"response = wait_for_result(celery_app, task.id, timeout=180)"
|
| 529 |
+
]
|
| 530 |
+
},
|
| 531 |
+
{
|
| 532 |
+
"cell_type": "code",
|
| 533 |
+
"execution_count": 8,
|
| 534 |
+
"id": "2872dd07-110a-4cdb-a905-588e31bdae49",
|
| 535 |
+
"metadata": {},
|
| 536 |
+
"outputs": [
|
| 537 |
+
{
|
| 538 |
+
"data": {
|
| 539 |
+
"text/html": [
|
| 540 |
+
"<style type=\"text/css\">\n",
|
| 541 |
+
"#T_dbf15 caption {\n",
|
| 542 |
+
" caption-side: top;\n",
|
| 543 |
+
" font-weight: 700;\n",
|
| 544 |
+
" font-size: 1.2rem;\n",
|
| 545 |
+
" margin-bottom: 10px;\n",
|
| 546 |
+
"}\n",
|
| 547 |
+
"#T_dbf15 th.col_heading {\n",
|
| 548 |
+
" font-weight: 700;\n",
|
| 549 |
+
" padding: 8px;\n",
|
| 550 |
+
" text-align: center;\n",
|
| 551 |
+
"}\n",
|
| 552 |
+
"#T_dbf15 td {\n",
|
| 553 |
+
" padding: 8px;\n",
|
| 554 |
+
" vertical-align: center;\n",
|
| 555 |
+
"}\n",
|
| 556 |
+
"#T_dbf15 table {\n",
|
| 557 |
+
" border-collapse: separate;\n",
|
| 558 |
+
" border-spacing: 0px;\n",
|
| 559 |
+
" width: 100%;\n",
|
| 560 |
+
"}\n",
|
| 561 |
+
"#T_dbf15_row0_col0, #T_dbf15_row1_col0, #T_dbf15_row2_col0 {\n",
|
| 562 |
+
" background-color: #fafafa;\n",
|
| 563 |
+
" text-align: left;\n",
|
| 564 |
+
" font-weight: 600;\n",
|
| 565 |
+
" font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;\n",
|
| 566 |
+
"}\n",
|
| 567 |
+
"#T_dbf15_row0_col1 {\n",
|
| 568 |
+
" background-color: #006837;\n",
|
| 569 |
+
" color: #f1f1f1;\n",
|
| 570 |
+
" font-weight: 500;\n",
|
| 571 |
+
"}\n",
|
| 572 |
+
"#T_dbf15_row1_col1 {\n",
|
| 573 |
+
" background-color: #42ac5a;\n",
|
| 574 |
+
" color: #f1f1f1;\n",
|
| 575 |
+
" font-weight: 500;\n",
|
| 576 |
+
"}\n",
|
| 577 |
+
"#T_dbf15_row2_col1 {\n",
|
| 578 |
+
" background-color: #70c164;\n",
|
| 579 |
+
" color: #000000;\n",
|
| 580 |
+
" font-weight: 500;\n",
|
| 581 |
+
"}\n",
|
| 582 |
+
"</style>\n",
|
| 583 |
+
"<table id=\"T_dbf15\">\n",
|
| 584 |
+
" <caption>Product Prediction Results</caption>\n",
|
| 585 |
+
" <thead>\n",
|
| 586 |
+
" <tr>\n",
|
| 587 |
+
" <th id=\"T_dbf15_level0_col0\" class=\"col_heading level0 col0\" >Predicted Reaction</th>\n",
|
| 588 |
+
" <th id=\"T_dbf15_level0_col1\" class=\"col_heading level0 col1\" >confidence</th>\n",
|
| 589 |
+
" </tr>\n",
|
| 590 |
+
" </thead>\n",
|
| 591 |
+
" <tbody>\n",
|
| 592 |
+
" <tr>\n",
|
| 593 |
+
" <td id=\"T_dbf15_row0_col0\" class=\"data row0 col0\" >CCI.O=Cc1ccc([N+](=O)[O-])c(O)c1 → CCOc1cc(C=O)ccc1[N+](=O)[O-]</td>\n",
|
| 594 |
+
" <td id=\"T_dbf15_row0_col1\" class=\"data row0 col1\" >0.999</td>\n",
|
| 595 |
+
" </tr>\n",
|
| 596 |
+
" <tr>\n",
|
| 597 |
+
" <td id=\"T_dbf15_row1_col0\" class=\"data row1 col0\" >CCI.O=Cc1ccc([N+](=O)[O-])c(O)c1 → CCOc1cc(C=O)ccc1[N+](=O)[O-].CCOc1cc(C=O)ccc1[N+](=O)[O-]</td>\n",
|
| 598 |
+
" <td id=\"T_dbf15_row1_col1\" class=\"data row1 col1\" >0.847</td>\n",
|
| 599 |
+
" </tr>\n",
|
| 600 |
+
" <tr>\n",
|
| 601 |
+
" <td id=\"T_dbf15_row2_col0\" class=\"data row2 col0\" >CCI.O=Cc1ccc([N+](=O)[O-])c(O)c1 → CCOc1cc(C=O)ccc1[N+](=O)[O-].CCOc1cc(CO)ccc1[N+](=O)[O-]</td>\n",
|
| 602 |
+
" <td id=\"T_dbf15_row2_col1\" class=\"data row2 col1\" >0.782</td>\n",
|
| 603 |
+
" </tr>\n",
|
| 604 |
+
" </tbody>\n",
|
| 605 |
+
"</table>\n"
|
| 606 |
+
],
|
| 607 |
+
"text/plain": [
|
| 608 |
+
"<IPython.core.display.HTML object>"
|
| 609 |
+
]
|
| 610 |
+
},
|
| 611 |
+
"metadata": {},
|
| 612 |
+
"output_type": "display_data"
|
| 613 |
+
}
|
| 614 |
+
],
|
| 615 |
+
"source": [
|
| 616 |
+
"# Build a dataframe with the result in the response\n",
|
| 617 |
+
"df = build_results_dataframe(response[\"result\"])\n",
|
| 618 |
+
"\n",
|
| 619 |
+
"# Create and show the data in a table\n",
|
| 620 |
+
"style_results_table(df, caption=\"Product Prediction Results\")"
|
| 621 |
+
]
|
| 622 |
+
},
|
| 623 |
+
{
|
| 624 |
+
"cell_type": "markdown",
|
| 625 |
+
"id": "ee465c64-f56c-4295-8f29-5adf23b494c1",
|
| 626 |
+
"metadata": {},
|
| 627 |
+
"source": [
|
| 628 |
+
"### Retrosynthesis prediction"
|
| 629 |
+
]
|
| 630 |
+
},
|
| 631 |
+
{
|
| 632 |
+
"cell_type": "code",
|
| 633 |
+
"execution_count": 9,
|
| 634 |
+
"id": "c46f70ed-2b19-43cb-b5d9-ae846cdc99c7",
|
| 635 |
+
"metadata": {},
|
| 636 |
+
"outputs": [
|
| 637 |
+
{
|
| 638 |
+
"name": "stdout",
|
| 639 |
+
"output_type": "stream",
|
| 640 |
+
"text": [
|
| 641 |
+
"Task sent. Assigned task_id: 2ac795d1-67f2-42ab-b6e8-db9d0f04e37d\n",
|
| 642 |
+
"State: PENDING | Info: None\n",
|
| 643 |
+
"State: STARTED | Info: None\n",
|
| 644 |
+
"State: SUCCESS | Info: {'result': [{'reactants': 'C1CCOC1.C=CC(=C)Cl.C[Si](C)(C)[CH2][Mg][Cl]', 'product': 'C=CC(=C)C[Si](C)(C)C', 'confidence': 0.8633497322540727, 'smiles': 'C1CCOC1.C=CC(=C)Cl.C[Si](C)(C)[CH2][Mg][Cl]>>C=CC(=C)C[Si](C)(C)C'}, {'reactants': 'C1CCOC1.C=CC(=C)Cl.C[Si](C)(C)[CH2][Mg][Cl].N#N', 'product': 'C=CC(=C)C[Si](C)(C)C', 'confidence': 0.853555662500899, 'smiles': 'C1CCOC1.C=CC(=C)Cl.C[Si](C)(C)[CH2][Mg][Cl].N#N>>C=CC(=C)C[Si](C)(C)C'}, {'reactants': 'C1CCOC1.C=CC(=C)Cl.CCOCC.C[Si](C)(C)[CH2][Mg][Cl]', 'product': 'C=CC(=C)C[Si](C)(C)C', 'confidence': 0.8437746350333076, 'smiles': 'C1CCOC1.C=CC(=C)Cl.CCOCC.C[Si](C)(C)[CH2][Mg][Cl]>>C=CC(=C)C[Si](C)(C)C'}, {'reactants': 'C1CCOC1.C=CC(=C)Cl.C[Si](C)(C)[CH2][Mg][Cl].O.[Cl-].[NH4+]', 'product': 'C=CC(=C)C[Si](C)(C)C', 'confidence': 0.8341045215463807, 'smiles': 'C1CCOC1.C=CC(=C)Cl.C[Si](C)(C)[CH2][Mg][Cl].O.[Cl-].[NH4+]>>C=CC(=C)C[Si](C)(C)C'}, {'reactants': 'C1CCOC1.C=CC(=C)Cl.CCOCC.C[Si](C)(C)[CH2][Mg][Cl].N#N', 'product': 'C=CC(=C)C[Si](C)(C)C', 'confidence': 0.833862536714611, 'smiles': 'C1CCOC1.C=CC(=C)Cl.CCOCC.C[Si](C)(C)[CH2][Mg][Cl].N#N>>C=CC(=C)C[Si](C)(C)C'}, {'reactants': 'C1CCOC1.C=CC(=C)Cl.CCOCC.C[Si](C)(C)[CH2][Mg][Cl].O.[Cl-].[NH4+]', 'product': 'C=CC(=C)C[Si](C)(C)C', 'confidence': 0.8328274628653165, 'smiles': 'C1CCOC1.C=CC(=C)Cl.CCOCC.C[Si](C)(C)[CH2][Mg][Cl].O.[Cl-].[NH4+]>>C=CC(=C)C[Si](C)(C)C'}, {'reactants': 'C1CCOC1.C=CC(=C)Cl.CCOCC.C[Si](C)(C)[CH2][Mg][Cl].Cl.O', 'product': 'C=CC(=C)C[Si](C)(C)C', 'confidence': 0.8303334989016697, 'smiles': 'C1CCOC1.C=CC(=C)Cl.CCOCC.C[Si](C)(C)[CH2][Mg][Cl].Cl.O>>C=CC(=C)C[Si](C)(C)C'}, {'reactants': 'C1CCOC1.C=CC(=C)Cl.CCOCC.C[Si](C)(C)[CH2][Mg][Cl].O.[Cl-].[Mg].[NH4+]', 'product': 'C=CC(=C)C[Si](C)(C)C', 'confidence': 0.8301065237221663, 'smiles': 'C1CCOC1.C=CC(=C)Cl.CCOCC.C[Si](C)(C)[CH2][Mg][Cl].O.[Cl-].[Mg].[NH4+]>>C=CC(=C)C[Si](C)(C)C'}], 'time': 14.295113801956177}\n"
|
| 645 |
+
]
|
| 646 |
+
}
|
| 647 |
+
],
|
| 648 |
+
"source": [
|
| 649 |
+
"# Choose product for retrosynthesis prediction\n",
|
| 650 |
+
"product = \"C=CC(=C)C[Si](C)(C)C\"\n",
|
| 651 |
+
"\n",
|
| 652 |
+
"# Setup task kwargs\n",
|
| 653 |
+
"kwargs = {\n",
|
| 654 |
+
" \"topn\": 15, # Number of results per reactant\n",
|
| 655 |
+
" \"num_beams\": 15, # Number of beams used for prediction. Must be >= topn\n",
|
| 656 |
+
" \"fap\": 0.6, # Forward likelihood acceptance probability (not length averaged)\n",
|
| 657 |
+
" \"fld\": 0.2, # Forward likelihood delta required between the top2 forward prediction results\n",
|
| 658 |
+
" \"device\": None, # Device used for predicting, either \"cuda\" or \"cpu\", None defaults to cuda if available\n",
|
| 659 |
+
" \"ckpt_forward\": \"Pistachio2025Q2-Forward\", # Default forward model\n",
|
| 660 |
+
" \"ckpt_retro\": \"Pistachio2025Q2-Retro\", # Default retrosynthesis model\n",
|
| 661 |
+
" \"vocab\": \"Pistachio2025Q2\", # Vocab for default forward and retrosynthesis models\n",
|
| 662 |
+
" # \"ckpt_forward_path\": \"models/forward/Pistachio2025Q2-Forward.ckpt\", # Can be used instead of ckpt_forward\n",
|
| 663 |
+
" # \"ckpt_retro_path\": \"models/retrosynthesis/Pistachio2025Q2-Retro.ckpt\", # Can be used instead of ckpt_retro\n",
|
| 664 |
+
" # \"vocab_path\": \"vocab/Pistachio2025Q2.txt\", # Can be used instead of vocab\n",
|
| 665 |
+
"}\n",
|
| 666 |
+
"\n",
|
| 667 |
+
"# Send the retro_prediction task with the product and kwargs\n",
|
| 668 |
+
"task = celery_app.send_task(\n",
|
| 669 |
+
" \"tasks.retro_prediction\",\n",
|
| 670 |
+
" [product],\n",
|
| 671 |
+
" kwargs=kwargs,\n",
|
| 672 |
+
" queue=\"retro_prediction\",\n",
|
| 673 |
+
")\n",
|
| 674 |
+
"print(\"Task sent. Assigned task_id: {}\".format(task.id))\n",
|
| 675 |
+
"\n",
|
| 676 |
+
"# Use the task id to get the result. Increase timeout if needed.\n",
|
| 677 |
+
"response = wait_for_result(celery_app, task.id, timeout=300)"
|
| 678 |
+
]
|
| 679 |
+
},
|
| 680 |
+
{
|
| 681 |
+
"cell_type": "code",
|
| 682 |
+
"execution_count": 10,
|
| 683 |
+
"id": "f7601ac3-2a27-43eb-956a-209c8dc3d4cc",
|
| 684 |
+
"metadata": {},
|
| 685 |
+
"outputs": [
|
| 686 |
+
{
|
| 687 |
+
"data": {
|
| 688 |
+
"text/html": [
|
| 689 |
+
"<style type=\"text/css\">\n",
|
| 690 |
+
"#T_90827 caption {\n",
|
| 691 |
+
" caption-side: top;\n",
|
| 692 |
+
" font-weight: 700;\n",
|
| 693 |
+
" font-size: 1.2rem;\n",
|
| 694 |
+
" margin-bottom: 10px;\n",
|
| 695 |
+
"}\n",
|
| 696 |
+
"#T_90827 th.col_heading {\n",
|
| 697 |
+
" font-weight: 700;\n",
|
| 698 |
+
" padding: 8px;\n",
|
| 699 |
+
" text-align: center;\n",
|
| 700 |
+
"}\n",
|
| 701 |
+
"#T_90827 td {\n",
|
| 702 |
+
" padding: 8px;\n",
|
| 703 |
+
" vertical-align: center;\n",
|
| 704 |
+
"}\n",
|
| 705 |
+
"#T_90827 table {\n",
|
| 706 |
+
" border-collapse: separate;\n",
|
| 707 |
+
" border-spacing: 0px;\n",
|
| 708 |
+
" width: 100%;\n",
|
| 709 |
+
"}\n",
|
| 710 |
+
"#T_90827_row0_col0, #T_90827_row1_col0, #T_90827_row2_col0, #T_90827_row3_col0, #T_90827_row4_col0, #T_90827_row5_col0, #T_90827_row6_col0, #T_90827_row7_col0 {\n",
|
| 711 |
+
" background-color: #fafafa;\n",
|
| 712 |
+
" text-align: right;\n",
|
| 713 |
+
" font-weight: 600;\n",
|
| 714 |
+
" font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;\n",
|
| 715 |
+
"}\n",
|
| 716 |
+
"#T_90827_row0_col1 {\n",
|
| 717 |
+
" background-color: #33a456;\n",
|
| 718 |
+
" color: #f1f1f1;\n",
|
| 719 |
+
" font-weight: 500;\n",
|
| 720 |
+
"}\n",
|
| 721 |
+
"#T_90827_row1_col1 {\n",
|
| 722 |
+
" background-color: #3ca959;\n",
|
| 723 |
+
" color: #f1f1f1;\n",
|
| 724 |
+
" font-weight: 500;\n",
|
| 725 |
+
"}\n",
|
| 726 |
+
"#T_90827_row2_col1 {\n",
|
| 727 |
+
" background-color: #42ac5a;\n",
|
| 728 |
+
" color: #f1f1f1;\n",
|
| 729 |
+
" font-weight: 500;\n",
|
| 730 |
+
"}\n",
|
| 731 |
+
"#T_90827_row3_col1, #T_90827_row4_col1, #T_90827_row5_col1 {\n",
|
| 732 |
+
" background-color: #4bb05c;\n",
|
| 733 |
+
" color: #f1f1f1;\n",
|
| 734 |
+
" font-weight: 500;\n",
|
| 735 |
+
"}\n",
|
| 736 |
+
"#T_90827_row6_col1, #T_90827_row7_col1 {\n",
|
| 737 |
+
" background-color: #4eb15d;\n",
|
| 738 |
+
" color: #f1f1f1;\n",
|
| 739 |
+
" font-weight: 500;\n",
|
| 740 |
+
"}\n",
|
| 741 |
+
"</style>\n",
|
| 742 |
+
"<table id=\"T_90827\">\n",
|
| 743 |
+
" <caption>Retrosynthesis Prediction Results</caption>\n",
|
| 744 |
+
" <thead>\n",
|
| 745 |
+
" <tr>\n",
|
| 746 |
+
" <th id=\"T_90827_level0_col0\" class=\"col_heading level0 col0\" >Predicted Reaction</th>\n",
|
| 747 |
+
" <th id=\"T_90827_level0_col1\" class=\"col_heading level0 col1\" >confidence</th>\n",
|
| 748 |
+
" </tr>\n",
|
| 749 |
+
" </thead>\n",
|
| 750 |
+
" <tbody>\n",
|
| 751 |
+
" <tr>\n",
|
| 752 |
+
" <td id=\"T_90827_row0_col0\" class=\"data row0 col0\" >C1CCOC1.C=CC(=C)Cl.C[Si](C)(C)[CH2][Mg][Cl] → C=CC(=C)C[Si](C)(C)C</td>\n",
|
| 753 |
+
" <td id=\"T_90827_row0_col1\" class=\"data row0 col1\" >0.863</td>\n",
|
| 754 |
+
" </tr>\n",
|
| 755 |
+
" <tr>\n",
|
| 756 |
+
" <td id=\"T_90827_row1_col0\" class=\"data row1 col0\" >C1CCOC1.C=CC(=C)Cl.C[Si](C)(C)[CH2][Mg][Cl].N#N → C=CC(=C)C[Si](C)(C)C</td>\n",
|
| 757 |
+
" <td id=\"T_90827_row1_col1\" class=\"data row1 col1\" >0.854</td>\n",
|
| 758 |
+
" </tr>\n",
|
| 759 |
+
" <tr>\n",
|
| 760 |
+
" <td id=\"T_90827_row2_col0\" class=\"data row2 col0\" >C1CCOC1.C=CC(=C)Cl.CCOCC.C[Si](C)(C)[CH2][Mg][Cl] → C=CC(=C)C[Si](C)(C)C</td>\n",
|
| 761 |
+
" <td id=\"T_90827_row2_col1\" class=\"data row2 col1\" >0.844</td>\n",
|
| 762 |
+
" </tr>\n",
|
| 763 |
+
" <tr>\n",
|
| 764 |
+
" <td id=\"T_90827_row3_col0\" class=\"data row3 col0\" >C1CCOC1.C=CC(=C)Cl.C[Si](C)(C)[CH2][Mg][Cl].O.[Cl-].[NH4+] → C=CC(=C)C[Si](C)(C)C</td>\n",
|
| 765 |
+
" <td id=\"T_90827_row3_col1\" class=\"data row3 col1\" >0.834</td>\n",
|
| 766 |
+
" </tr>\n",
|
| 767 |
+
" <tr>\n",
|
| 768 |
+
" <td id=\"T_90827_row4_col0\" class=\"data row4 col0\" >C1CCOC1.C=CC(=C)Cl.CCOCC.C[Si](C)(C)[CH2][Mg][Cl].N#N → C=CC(=C)C[Si](C)(C)C</td>\n",
|
| 769 |
+
" <td id=\"T_90827_row4_col1\" class=\"data row4 col1\" >0.834</td>\n",
|
| 770 |
+
" </tr>\n",
|
| 771 |
+
" <tr>\n",
|
| 772 |
+
" <td id=\"T_90827_row5_col0\" class=\"data row5 col0\" >C1CCOC1.C=CC(=C)Cl.CCOCC.C[Si](C)(C)[CH2][Mg][Cl].O.[Cl-].[NH4+] → C=CC(=C)C[Si](C)(C)C</td>\n",
|
| 773 |
+
" <td id=\"T_90827_row5_col1\" class=\"data row5 col1\" >0.833</td>\n",
|
| 774 |
+
" </tr>\n",
|
| 775 |
+
" <tr>\n",
|
| 776 |
+
" <td id=\"T_90827_row6_col0\" class=\"data row6 col0\" >C1CCOC1.C=CC(=C)Cl.CCOCC.C[Si](C)(C)[CH2][Mg][Cl].Cl.O → C=CC(=C)C[Si](C)(C)C</td>\n",
|
| 777 |
+
" <td id=\"T_90827_row6_col1\" class=\"data row6 col1\" >0.830</td>\n",
|
| 778 |
+
" </tr>\n",
|
| 779 |
+
" <tr>\n",
|
| 780 |
+
" <td id=\"T_90827_row7_col0\" class=\"data row7 col0\" >C1CCOC1.C=CC(=C)Cl.CCOCC.C[Si](C)(C)[CH2][Mg][Cl].O.[Cl-].[Mg].[NH4+] → C=CC(=C)C[Si](C)(C)C</td>\n",
|
| 781 |
+
" <td id=\"T_90827_row7_col1\" class=\"data row7 col1\" >0.830</td>\n",
|
| 782 |
+
" </tr>\n",
|
| 783 |
+
" </tbody>\n",
|
| 784 |
+
"</table>\n"
|
| 785 |
+
],
|
| 786 |
+
"text/plain": [
|
| 787 |
+
"<IPython.core.display.HTML object>"
|
| 788 |
+
]
|
| 789 |
+
},
|
| 790 |
+
"metadata": {},
|
| 791 |
+
"output_type": "display_data"
|
| 792 |
+
}
|
| 793 |
+
],
|
| 794 |
+
"source": [
|
| 795 |
+
"# Build a dataframe with the result in the response\n",
|
| 796 |
+
"df = build_results_dataframe(response[\"result\"])\n",
|
| 797 |
+
"\n",
|
| 798 |
+
"# Create and show the data in a table\n",
|
| 799 |
+
"style_results_table(df, caption=\"Retrosynthesis Prediction Results\", align=\"right\")"
|
| 800 |
+
]
|
| 801 |
+
},
|
| 802 |
+
{
|
| 803 |
+
"cell_type": "markdown",
|
| 804 |
+
"id": "98ba77e3-fcf3-488a-b51c-54ce2dcfe7e0",
|
| 805 |
+
"metadata": {},
|
| 806 |
+
"source": [
|
| 807 |
+
"### Retro tree prediction"
|
| 808 |
+
]
|
| 809 |
+
},
|
| 810 |
+
{
|
| 811 |
+
"cell_type": "code",
|
| 812 |
+
"execution_count": 11,
|
| 813 |
+
"id": "4cdba192-ec0c-494d-b356-b48cbdeb5cb8",
|
| 814 |
+
"metadata": {},
|
| 815 |
+
"outputs": [
|
| 816 |
+
{
|
| 817 |
+
"name": "stdout",
|
| 818 |
+
"output_type": "stream",
|
| 819 |
+
"text": [
|
| 820 |
+
"Task sent. Assigned task_id: 26ea63e0-2527-4baf-9905-673f5d98442e\n",
|
| 821 |
+
"State: PENDING | Info: None\n",
|
| 822 |
+
"State: STARTED | Info: None\n",
|
| 823 |
+
"State: SUCCESS | Info: {'result': [{'open_nodes': ['ClCCl', '[Cl][Ru]([Cl])(=[CH]c1ccccc1)([P](C1CCCCC1)(C1CCCCC1)C1CCCCC1)[P](C1CCCCC1)(C1CCCCC1)C1CCCCC1', 'C=CCC1C(=O)OC(=O)C1CC(=C)C[Si](C)(C)C'], 'expanded': ['C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1'], 'score': 0.7056475777714836, 'steps': 1, 'arcs': [{'product': 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1', 'reactants': ['C=CCC1C(=O)OC(=O)C1CC(=C)C[Si](C)(C)C', 'ClCCl', '[Cl][Ru]([Cl])(=[CH]c1ccccc1)([P](C1CCCCC1)(C1CCCCC1)C1CCCCC1)[P](C1CCCCC1)(C1CCCCC1)C1CCCCC1'], 'forward_likelihood': 0.8842267990112305, 'arc_score': 0.7056475777704836, 'confidence': 0.9439050974158504, 'metadata': {'reactants': 'C=CCC1C(=O)OC(=O)C1CC(=C)C[Si](C)(C)C.ClCCl.[Cl][Ru]([Cl])(=[CH]c1ccccc1)([P](C1CCCCC1)(C1CCCCC1)C1CCCCC1)[P](C1CCCCC1)(C1CCCCC1)C1CCCCC1', 'product': 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1', 'confidence': 0.9439050974158504, 'smiles': 'C=CCC1C(=O)OC(=O)C1CC(=C)C[Si](C)(C)C.ClCCl.[Cl][Ru]([Cl])(=[CH]c1ccccc1)([P](C1CCCCC1)(C1CCCCC1)C1CCCCC1)[P](C1CCCCC1)(C1CCCCC1)C1CCCCC1>>C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1'}}]}, {'open_nodes': ['ClCCl', 'Cc1cc(C)c(N2CCN(c3c(C)cc(C)cc3C)[C]2=[Ru]([Cl])([Cl])(=[CH]c2ccccc2)[P](C2CCCCC2)(C2CCCCC2)C2CCCCC2)c(C)c1', 'C=CCC1C(=O)OC(=O)C1CC(=C)C[Si](C)(C)C'], 'expanded': ['C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1'], 'score': 0.37149402762328515, 'steps': 1, 'arcs': [{'product': 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1', 'reactants': ['C=CCC1C(=O)OC(=O)C1CC(=C)C[Si](C)(C)C', 'Cc1cc(C)c(N2CCN(c3c(C)cc(C)cc3C)[C]2=[Ru]([Cl])([Cl])(=[CH]c2ccccc2)[P](C2CCCCC2)(C2CCCCC2)C2CCCCC2)c(C)c1', 'ClCCl'], 'forward_likelihood': 0.8087736368179321, 'arc_score': 0.3714940276222852, 'confidence': 0.9503329457084253, 'metadata': {'reactants': 'C=CCC1C(=O)OC(=O)C1CC(=C)C[Si](C)(C)C.Cc1cc(C)c(N2CCN(c3c(C)cc(C)cc3C)[C]2=[Ru]([Cl])([Cl])(=[CH]c2ccccc2)[P](C2CCCCC2)(C2CCCCC2)C2CCCCC2)c(C)c1.ClCCl', 'product': 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1', 'confidence': 0.9503329457084253, 'smiles': 'C=CCC1C(=O)OC(=O)C1CC(=C)C[Si](C)(C)C.Cc1cc(C)c(N2CCN(c3c(C)cc(C)cc3C)[C]2=[Ru]([Cl])([Cl])(=[CH]c2ccccc2)[P](C2CCCCC2)(C2CCCCC2)C2CCCCC2)c(C)c1.ClCCl>>C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1'}}]}, {'open_nodes': ['ClCCl', 'C=CCC1C(=O)OC(=O)C1CC(=C[Si](C)(C)C)C[Si](C)(C)C', 'Cc1cc(C)c(N2CCN(c3c(C)cc(C)cc3C)[C]2=[Ru]([Cl])([Cl])(=[CH]c2ccccc2)[P](C2CCCCC2)(C2CCCCC2)C2CCCCC2)c(C)c1'], 'expanded': ['C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1'], 'score': 0.33907517755787414, 'steps': 1, 'arcs': [{'product': 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1', 'reactants': ['C=CCC1C(=O)OC(=O)C1CC(=C[Si](C)(C)C)C[Si](C)(C)C', 'Cc1cc(C)c(N2CCN(c3c(C)cc(C)cc3C)[C]2=[Ru]([Cl])([Cl])(=[CH]c2ccccc2)[P](C2CCCCC2)(C2CCCCC2)C2CCCCC2)c(C)c1', 'ClCCl'], 'forward_likelihood': 0.8071097731590271, 'arc_score': 0.3390751775568742, 'confidence': 0.9455194125006082, 'metadata': {'reactants': 'C=CCC1C(=O)OC(=O)C1CC(=C[Si](C)(C)C)C[Si](C)(C)C.Cc1cc(C)c(N2CCN(c3c(C)cc(C)cc3C)[C]2=[Ru]([Cl])([Cl])(=[CH]c2ccccc2)[P](C2CCCCC2)(C2CCCCC2)C2CCCCC2)c(C)c1.ClCCl', 'product': 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1', 'confidence': 0.9455194125006082, 'smiles': 'C=CCC1C(=O)OC(=O)C1CC(=C[Si](C)(C)C)C[Si](C)(C)C.Cc1cc(C)c(N2CCN(c3c(C)cc(C)cc3C)[C]2=[Ru]([Cl])([Cl])(=[CH]c2ccccc2)[P](C2CCCCC2)(C2CCCCC2)C2CCCCC2)c(C)c1.ClCCl>>C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1'}}]}, {'open_nodes': ['C1CCOC1', 'O=C1C=CC(=O)O1', 'C=CC(=C)O', 'ClP(Cl)(Cl)(Cl)Cl', 'Cc1ccccc1', 'C[Si](C)(C)[CH2][Mg][Cl]'], 'expanded': ['C=CC(=C)C[Si](C)(C)C', 'C=CC(=C)Cl', 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1'], 'score': 0.24857583642032657, 'steps': 3, 'arcs': [{'product': 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1', 'reactants': ['C=CC(=C)C[Si](C)(C)C', 'Cc1ccccc1', 'O=C1C=CC(=O)O1'], 'forward_likelihood': 0.39315515756607056, 'arc_score': 0.43673660563264405, 'confidence': 0.9271861522313083, 'metadata': {'reactants': 'C=CC(=C)C[Si](C)(C)C.Cc1ccccc1.O=C1C=CC(=O)O1', 'product': 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1', 'confidence': 0.9271861522313083, 'smiles': 'C=CC(=C)C[Si](C)(C)C.Cc1ccccc1.O=C1C=CC(=O)O1>>C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1'}}, {'product': 'C=CC(=C)C[Si](C)(C)C', 'reactants': ['C1CCOC1', 'C=CC(=C)Cl', 'C[Si](C)(C)[CH2][Mg][Cl]'], 'forward_likelihood': 0.9396551847457886, 'arc_score': 1.0504772488213368, 'confidence': 0.8633497322540727, 'metadata': {'reactants': 'C1CCOC1.C=CC(=C)Cl.C[Si](C)(C)[CH2][Mg][Cl]', 'product': 'C=CC(=C)C[Si](C)(C)C', 'confidence': 0.8633497322540727, 'smiles': 'C1CCOC1.C=CC(=C)Cl.C[Si](C)(C)[CH2][Mg][Cl]>>C=CC(=C)C[Si](C)(C)C'}}, {'product': 'C=CC(=C)Cl', 'reactants': ['C=CC(=C)O', 'ClP(Cl)(Cl)(Cl)Cl'], 'forward_likelihood': 0.5561506152153015, 'arc_score': 0.541817045936755, 'confidence': 0.8283158501723433, 'metadata': {'reactants': 'C=CC(=C)O.ClP(Cl)(Cl)(Cl)Cl', 'product': 'C=CC(=C)Cl', 'confidence': 0.8283158501723433, 'smiles': 'C=CC(=C)O.ClP(Cl)(Cl)(Cl)Cl>>C=CC(=C)Cl'}}]}, {'open_nodes': ['O=C1C=CC(=O)O1', '[NH4+]', 'C=CC(=C)O', 'ClP(Cl)(Cl)(Cl)Cl', '[Cl-]', 'Cc1ccccc1', 'C[Si](C)(C)[CH2][Mg][Cl]', 'C1CCOC1', 'O'], 'expanded': ['C=CC(=C)C[Si](C)(C)C', 'C=CC(=C)Cl', 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1'], 'score': 0.2424893468373566, 'steps': 3, 'arcs': [{'product': 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1', 'reactants': ['C=CC(=C)C[Si](C)(C)C', 'Cc1ccccc1', 'O=C1C=CC(=O)O1'], 'forward_likelihood': 0.39315515756607056, 'arc_score': 0.43673660563264405, 'confidence': 0.9271861522313083, 'metadata': {'reactants': 'C=CC(=C)C[Si](C)(C)C.Cc1ccccc1.O=C1C=CC(=O)O1', 'product': 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1', 'confidence': 0.9271861522313083, 'smiles': 'C=CC(=C)C[Si](C)(C)C.Cc1ccccc1.O=C1C=CC(=O)O1>>C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1'}}, {'product': 'C=CC(=C)C[Si](C)(C)C', 'reactants': ['C1CCOC1', 'C=CC(=C)Cl', 'C[Si](C)(C)[CH2][Mg][Cl]', 'O', '[Cl-]', '[NH4+]'], 'forward_likelihood': 0.9166916608810425, 'arc_score': 1.02475584756135, 'confidence': 0.8341045215463807, 'metadata': {'reactants': 'C1CCOC1.C=CC(=C)Cl.C[Si](C)(C)[CH2][Mg][Cl].O.[Cl-].[NH4+]', 'product': 'C=CC(=C)C[Si](C)(C)C', 'confidence': 0.8341045215463807, 'smiles': 'C1CCOC1.C=CC(=C)Cl.C[Si](C)(C)[CH2][Mg][Cl].O.[Cl-].[NH4+]>>C=CC(=C)C[Si](C)(C)C'}}, {'product': 'C=CC(=C)Cl', 'reactants': ['C=CC(=C)O', 'ClP(Cl)(Cl)(Cl)Cl'], 'forward_likelihood': 0.5561506152153015, 'arc_score': 0.541817045936755, 'confidence': 0.8283158501723433, 'metadata': {'reactants': 'C=CC(=C)O.ClP(Cl)(Cl)(Cl)Cl', 'product': 'C=CC(=C)Cl', 'confidence': 0.8283158501723433, 'smiles': 'C=CC(=C)O.ClP(Cl)(Cl)(Cl)Cl>>C=CC(=C)Cl'}}]}, {'open_nodes': ['O=C1C=CC(=O)O1', 'C=CC(=C)O', 'CCOCC', 'ClP(Cl)(Cl)(Cl)Cl', 'Cc1ccccc1', 'C[Si](C)(C)[CH2][Mg][Cl]', 'C1CCOC1', 'Cl', 'O'], 'expanded': ['C=CC(=C)C[Si](C)(C)C', 'C=CC(=C)Cl', 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1'], 'score': 0.23326155626917652, 'steps': 3, 'arcs': [{'product': 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1', 'reactants': ['C=CC(=C)C[Si](C)(C)C', 'Cc1ccccc1', 'O=C1C=CC(=O)O1'], 'forward_likelihood': 0.39315515756607056, 'arc_score': 0.43673660563264405, 'confidence': 0.9271861522313083, 'metadata': {'reactants': 'C=CC(=C)C[Si](C)(C)C.Cc1ccccc1.O=C1C=CC(=O)O1', 'product': 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1', 'confidence': 0.9271861522313083, 'smiles': 'C=CC(=C)C[Si](C)(C)C.Cc1ccccc1.O=C1C=CC(=O)O1>>C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1'}}, {'product': 'C=CC(=C)C[Si](C)(C)C', 'reactants': ['C1CCOC1', 'C=CC(=C)Cl', 'CCOCC', 'C[Si](C)(C)[CH2][Mg][Cl]', 'Cl', 'O'], 'forward_likelihood': 0.8817673325538635, 'arc_score': 0.9857593618676268, 'confidence': 0.8303334989016697, 'metadata': {'reactants': 'C1CCOC1.C=CC(=C)Cl.CCOCC.C[Si](C)(C)[CH2][Mg][Cl].Cl.O', 'product': 'C=CC(=C)C[Si](C)(C)C', 'confidence': 0.8303334989016697, 'smiles': 'C1CCOC1.C=CC(=C)Cl.CCOCC.C[Si](C)(C)[CH2][Mg][Cl].Cl.O>>C=CC(=C)C[Si](C)(C)C'}}, {'product': 'C=CC(=C)Cl', 'reactants': ['C=CC(=C)O', 'ClP(Cl)(Cl)(Cl)Cl'], 'forward_likelihood': 0.5561506152153015, 'arc_score': 0.541817045936755, 'confidence': 0.8283158501723433, 'metadata': {'reactants': 'C=CC(=C)O.ClP(Cl)(Cl)(Cl)Cl', 'product': 'C=CC(=C)Cl', 'confidence': 0.8283158501723433, 'smiles': 'C=CC(=C)O.ClP(Cl)(Cl)(Cl)Cl>>C=CC(=C)Cl'}}]}, {'open_nodes': ['O=C1C=CC(=O)O1', '[NH4+]', 'C=CC(=C)O', 'CCOCC', '[Cl-]', 'ClP(Cl)(Cl)(Cl)Cl', 'Cc1ccccc1', 'C[Si](C)(C)[CH2][Mg][Cl]', 'C1CCOC1', 'O', '[Mg]'], 'expanded': ['C=CC(=C)C[Si](C)(C)C', 'C=CC(=C)Cl', 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1'], 'score': 0.21788071070873768, 'steps': 3, 'arcs': [{'product': 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1', 'reactants': ['C=CC(=C)C[Si](C)(C)C', 'Cc1ccccc1', 'O=C1C=CC(=O)O1'], 'forward_likelihood': 0.39315515756607056, 'arc_score': 0.43673660563264405, 'confidence': 0.9271861522313083, 'metadata': {'reactants': 'C=CC(=C)C[Si](C)(C)C.Cc1ccccc1.O=C1C=CC(=O)O1', 'product': 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1', 'confidence': 0.9271861522313083, 'smiles': 'C=CC(=C)C[Si](C)(C)C.Cc1ccccc1.O=C1C=CC(=O)O1>>C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1'}}, {'product': 'C=CC(=C)C[Si](C)(C)C', 'reactants': ['C1CCOC1', 'C=CC(=C)Cl', 'CCOCC', 'C[Si](C)(C)[CH2][Mg][Cl]', 'O', '[Cl-]', '[Mg]', '[NH4+]'], 'forward_likelihood': 0.8617748022079468, 'arc_score': 0.9207601706285793, 'confidence': 0.8301065237221663, 'metadata': {'reactants': 'C1CCOC1.C=CC(=C)Cl.CCOCC.C[Si](C)(C)[CH2][Mg][Cl].O.[Cl-].[Mg].[NH4+]', 'product': 'C=CC(=C)C[Si](C)(C)C', 'confidence': 0.8301065237221663, 'smiles': 'C1CCOC1.C=CC(=C)Cl.CCOCC.C[Si](C)(C)[CH2][Mg][Cl].O.[Cl-].[Mg].[NH4+]>>C=CC(=C)C[Si](C)(C)C'}}, {'product': 'C=CC(=C)Cl', 'reactants': ['C=CC(=C)O', 'ClP(Cl)(Cl)(Cl)Cl'], 'forward_likelihood': 0.5561506152153015, 'arc_score': 0.541817045936755, 'confidence': 0.8283158501723433, 'metadata': {'reactants': 'C=CC(=C)O.ClP(Cl)(Cl)(Cl)Cl', 'product': 'C=CC(=C)Cl', 'confidence': 0.8283158501723433, 'smiles': 'C=CC(=C)O.ClP(Cl)(Cl)(Cl)Cl>>C=CC(=C)Cl'}}]}, {'open_nodes': ['C1CCOC1', 'O=C1C=CC(=O)O1', 'C=CC(=C)O', 'O=S(Cl)Cl', 'Cc1ccccc1', 'C[Si](C)(C)[CH2][Mg][Cl]'], 'expanded': ['C=CC(=C)C[Si](C)(C)C', 'C=CC(=C)Cl', 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1'], 'score': 0.18048843471208253, 'steps': 3, 'arcs': [{'product': 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1', 'reactants': ['C=CC(=C)C[Si](C)(C)C', 'Cc1ccccc1', 'O=C1C=CC(=O)O1'], 'forward_likelihood': 0.39315515756607056, 'arc_score': 0.43673660563264405, 'confidence': 0.9271861522313083, 'metadata': {'reactants': 'C=CC(=C)C[Si](C)(C)C.Cc1ccccc1.O=C1C=CC(=O)O1', 'product': 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1', 'confidence': 0.9271861522313083, 'smiles': 'C=CC(=C)C[Si](C)(C)C.Cc1ccccc1.O=C1C=CC(=O)O1>>C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1'}}, {'product': 'C=CC(=C)C[Si](C)(C)C', 'reactants': ['C1CCOC1', 'C=CC(=C)Cl', 'C[Si](C)(C)[CH2][Mg][Cl]'], 'forward_likelihood': 0.9396551847457886, 'arc_score': 1.0504772488213368, 'confidence': 0.8633497322540727, 'metadata': {'reactants': 'C1CCOC1.C=CC(=C)Cl.C[Si](C)(C)[CH2][Mg][Cl]', 'product': 'C=CC(=C)C[Si](C)(C)C', 'confidence': 0.8633497322540727, 'smiles': 'C1CCOC1.C=CC(=C)Cl.C[Si](C)(C)[CH2][Mg][Cl]>>C=CC(=C)C[Si](C)(C)C'}}, {'product': 'C=CC(=C)Cl', 'reactants': ['C=CC(=C)O', 'O=S(Cl)Cl'], 'forward_likelihood': 0.403808057308197, 'arc_score': 0.39340795118967853, 'confidence': 0.8269444351225532, 'metadata': {'reactants': 'C=CC(=C)O.O=S(Cl)Cl', 'product': 'C=CC(=C)Cl', 'confidence': 0.8269444351225532, 'smiles': 'C=CC(=C)O.O=S(Cl)Cl>>C=CC(=C)Cl'}}]}, {'open_nodes': ['O=C1OC(=O)C2CC=CCC12', 'Cc1cc(C)c(N2CCN(c3c(C)cc(C)cc3C)[C]2=[Ru]([Cl])([Cl])=[CH]c2ccccc2)c(C)c1', 'ClCCl', 'C=CC(=C)C[Si](C)(C)C', 'C1CCC(P(C2CCCCC2)C2CCCCC2)CC1', 'N#N'], 'expanded': ['C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1'], 'score': 0.1796396903569043, 'steps': 1, 'arcs': [{'product': 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1', 'reactants': ['C1CCC(P(C2CCCCC2)C2CCCCC2)CC1', 'C=CC(=C)C[Si](C)(C)C', 'Cc1cc(C)c(N2CCN(c3c(C)cc(C)cc3C)[C]2=[Ru]([Cl])([Cl])=[CH]c2ccccc2)c(C)c1', 'ClCCl', 'N#N', 'O=C1OC(=O)C2CC=CCC12'], 'forward_likelihood': 0.395216703414917, 'arc_score': 0.1796396903559043, 'confidence': 0.9290369156214755, 'metadata': {'reactants': 'C1CCC(P(C2CCCCC2)C2CCCCC2)CC1.C=CC(=C)C[Si](C)(C)C.Cc1cc(C)c(N2CCN(c3c(C)cc(C)cc3C)[C]2=[Ru]([Cl])([Cl])=[CH]c2ccccc2)c(C)c1.ClCCl.N#N.O=C1OC(=O)C2CC=CCC12', 'product': 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1', 'confidence': 0.9290369156214755, 'smiles': 'C1CCC(P(C2CCCCC2)C2CCCCC2)CC1.C=CC(=C)C[Si](C)(C)C.Cc1cc(C)c(N2CCN(c3c(C)cc(C)cc3C)[C]2=[Ru]([Cl])([Cl])=[CH]c2ccccc2)c(C)c1.ClCCl.N#N.O=C1OC(=O)C2CC=CCC12>>C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1'}}]}, {'open_nodes': ['O=C1C=CC(=O)O1', '[NH4+]', 'C=CC(=C)O', '[Cl-]', 'O=S(Cl)Cl', 'Cc1ccccc1', 'C[Si](C)(C)[CH2][Mg][Cl]', 'C1CCOC1', 'O'], 'expanded': ['C=CC(=C)C[Si](C)(C)C', 'C=CC(=C)Cl', 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1'], 'score': 0.17606909535254775, 'steps': 3, 'arcs': [{'product': 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1', 'reactants': ['C=CC(=C)C[Si](C)(C)C', 'Cc1ccccc1', 'O=C1C=CC(=O)O1'], 'forward_likelihood': 0.39315515756607056, 'arc_score': 0.43673660563264405, 'confidence': 0.9271861522313083, 'metadata': {'reactants': 'C=CC(=C)C[Si](C)(C)C.Cc1ccccc1.O=C1C=CC(=O)O1', 'product': 'C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1', 'confidence': 0.9271861522313083, 'smiles': 'C=CC(=C)C[Si](C)(C)C.Cc1ccccc1.O=C1C=CC(=O)O1>>C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1'}}, {'product': 'C=CC(=C)C[Si](C)(C)C', 'reactants': ['C1CCOC1', 'C=CC(=C)Cl', 'C[Si](C)(C)[CH2][Mg][Cl]', 'O', '[Cl-]', '[NH4+]'], 'forward_likelihood': 0.9166916608810425, 'arc_score': 1.02475584756135, 'confidence': 0.8341045215463807, 'metadata': {'reactants': 'C1CCOC1.C=CC(=C)Cl.C[Si](C)(C)[CH2][Mg][Cl].O.[Cl-].[NH4+]', 'product': 'C=CC(=C)C[Si](C)(C)C', 'confidence': 0.8341045215463807, 'smiles': 'C1CCOC1.C=CC(=C)Cl.C[Si](C)(C)[CH2][Mg][Cl].O.[Cl-].[NH4+]>>C=CC(=C)C[Si](C)(C)C'}}, {'product': 'C=CC(=C)Cl', 'reactants': ['C=CC(=C)O', 'O=S(Cl)Cl'], 'forward_likelihood': 0.403808057308197, 'arc_score': 0.39340795118967853, 'confidence': 0.8269444351225532, 'metadata': {'reactants': 'C=CC(=C)O.O=S(Cl)Cl', 'product': 'C=CC(=C)Cl', 'confidence': 0.8269444351225532, 'smiles': 'C=CC(=C)O.O=S(Cl)Cl>>C=CC(=C)Cl'}}]}], 'time': 1306.3053419589996}\n"
|
| 824 |
+
]
|
| 825 |
+
}
|
| 826 |
+
],
|
| 827 |
+
"source": [
|
| 828 |
+
"# Choose product for retrosynthesis tree prediction\n",
|
| 829 |
+
"product = \"C1C(C[Si](C)(C)C)=CCC2C(=O)OC(=O)C12\"\n",
|
| 830 |
+
"#product = \"Cc1cc2scnc2cc1N\"\n",
|
| 831 |
+
"#product = \"CC(C)(C(=O)O)C1C=CC=C(C2CC2)C1=O\"\n",
|
| 832 |
+
"#product = \"Nc1ccc2scnc2c1Br\"\n",
|
| 833 |
+
"\n",
|
| 834 |
+
"# Setup task kwargs\n",
|
| 835 |
+
"kwargs = {\n",
|
| 836 |
+
" \"topn\": 15, # Number of results per reactant\n",
|
| 837 |
+
" \"num_beams\": 15, # Number of beams used for prediction. Must be >= topn\n",
|
| 838 |
+
" \"fap\": 0.6, # Forward likelihood acceptance probability (not length averaged)\n",
|
| 839 |
+
" \"fld\": 0.2, # Forward likelihood delta required between the top2 forward prediction results\n",
|
| 840 |
+
" \"max_depth\": 4, # Max depth of the retrosynthesis tree\n",
|
| 841 |
+
" \"beam_width\": 6, # Max amount of nodes being expanded in each step\n",
|
| 842 |
+
" \"device\": None, # Device used for predicting, either \"cuda\" or \"cpu\", None defaults to cuda if available\n",
|
| 843 |
+
" \"ckpt_forward\": \"Pistachio2025Q2-Forward\", # Default forward model\n",
|
| 844 |
+
" \"ckpt_retro\": \"Pistachio2025Q2-Retro\", # Default retrosynthesis model\n",
|
| 845 |
+
" \"vocab\": \"Pistachio2025Q2\", # Vocab for default forward and retrosynthesis models\n",
|
| 846 |
+
" # \"ckpt_forward_path\": \"models/forward/Pistachio2025Q2-Forward.ckpt\", # Can be used instead of ckpt_forward\n",
|
| 847 |
+
" # \"ckpt_retro_path\": \"models/retrosynthesis/Pistachio2025Q2-Retro.ckpt\", # Can be used instead of ckpt_retro\n",
|
| 848 |
+
" # \"vocab_path\": \"vocab/Pistachio2025Q2.txt\", # Can be used instead of vocab\n",
|
| 849 |
+
"}\n",
|
| 850 |
+
"\n",
|
| 851 |
+
"# Send the retro_prediction_tree task with the product and kwargs\n",
|
| 852 |
+
"task = celery_app.send_task(\n",
|
| 853 |
+
" \"tasks.retro_prediction_tree\",\n",
|
| 854 |
+
" [product],\n",
|
| 855 |
+
" kwargs=kwargs,\n",
|
| 856 |
+
" queue=\"retro_prediction\",\n",
|
| 857 |
+
")\n",
|
| 858 |
+
"print(\"Task sent. Assigned task_id: {}\".format(task.id))\n",
|
| 859 |
+
"\n",
|
| 860 |
+
"# Use the task id to get the result. Increase timeout if needed.\n",
|
| 861 |
+
"response = wait_for_result(celery_app, task.id, timeout=3000)"
|
| 862 |
+
]
|
| 863 |
+
},
|
| 864 |
+
{
|
| 865 |
+
"cell_type": "code",
|
| 866 |
+
"execution_count": 12,
|
| 867 |
+
"id": "ee06bf27-b7ac-4d07-bff1-2cf99322b80d",
|
| 868 |
+
"metadata": {},
|
| 869 |
+
"outputs": [
|
| 870 |
+
{
|
| 871 |
+
"data": {
|
| 872 |
+
"text/markdown": [
|
| 873 |
+
"## Route 1\n",
|
| 874 |
+
"- **Score:** `0.706`\n",
|
| 875 |
+
"- **Steps:** `1`"
|
| 876 |
+
],
|
| 877 |
+
"text/plain": [
|
| 878 |
+
"<IPython.core.display.Markdown object>"
|
| 879 |
+
]
|
| 880 |
+
},
|
| 881 |
+
"metadata": {},
|
| 882 |
+
"output_type": "display_data"
|
| 883 |
+
},
|
| 884 |
+
{
|
| 885 |
+
"data": {
|
| 886 |
+
"text/markdown": [
|
| 887 |
+
"**Remaining molecules:** `ClCCl, [Cl][Ru]([Cl])(=[CH]c1ccccc1)([P](C1CCCCC1)(C1CCCCC1)C1CCCCC1)[P](C1CCCCC1)(C1CCCCC1)C1CCCCC1, C=CCC1C(=O)OC(=O)C1CC(=C)C[Si](C)(C)C`"
|
| 888 |
+
],
|
| 889 |
+
"text/plain": [
|
| 890 |
+
"<IPython.core.display.Markdown object>"
|
| 891 |
+
]
|
| 892 |
+
},
|
| 893 |
+
"metadata": {},
|
| 894 |
+
"output_type": "display_data"
|
| 895 |
+
},
|
| 896 |
+
{
|
| 897 |
+
"data": {
|
| 898 |
+
"text/markdown": [
|
| 899 |
+
"#### Step 1\n",
|
| 900 |
+
"**Product:** `C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1` \n",
|
| 901 |
+
"**Reactants:** `C=CCC1C(=O)OC(=O)C1CC(=C)C[Si](C)(C)C.ClCCl.[Cl][Ru]([Cl])(=[CH]c1ccccc1)([P](C1CCCCC1)(C1CCCCC1)C1CCCCC1)[P](C1CCCCC1)(C1CCCCC1)C1CCCCC1` \n",
|
| 902 |
+
"- Forward likelihood: `0.884` \n",
|
| 903 |
+
"- Arc score: `0.706` \n",
|
| 904 |
+
"- Retro confidence: `0.9439050974158504`"
|
| 905 |
+
],
|
| 906 |
+
"text/plain": [
|
| 907 |
+
"<IPython.core.display.Markdown object>"
|
| 908 |
+
]
|
| 909 |
+
},
|
| 910 |
+
"metadata": {},
|
| 911 |
+
"output_type": "display_data"
|
| 912 |
+
},
|
| 913 |
+
{
|
| 914 |
+
"data": {
|
| 915 |
+
"text/markdown": [
|
| 916 |
+
"## Route 2\n",
|
| 917 |
+
"- **Score:** `0.371`\n",
|
| 918 |
+
"- **Steps:** `1`"
|
| 919 |
+
],
|
| 920 |
+
"text/plain": [
|
| 921 |
+
"<IPython.core.display.Markdown object>"
|
| 922 |
+
]
|
| 923 |
+
},
|
| 924 |
+
"metadata": {},
|
| 925 |
+
"output_type": "display_data"
|
| 926 |
+
},
|
| 927 |
+
{
|
| 928 |
+
"data": {
|
| 929 |
+
"text/markdown": [
|
| 930 |
+
"**Remaining molecules:** `ClCCl, Cc1cc(C)c(N2CCN(c3c(C)cc(C)cc3C)[C]2=[Ru]([Cl])([Cl])(=[CH]c2ccccc2)[P](C2CCCCC2)(C2CCCCC2)C2CCCCC2)c(C)c1, C=CCC1C(=O)OC(=O)C1CC(=C)C[Si](C)(C)C`"
|
| 931 |
+
],
|
| 932 |
+
"text/plain": [
|
| 933 |
+
"<IPython.core.display.Markdown object>"
|
| 934 |
+
]
|
| 935 |
+
},
|
| 936 |
+
"metadata": {},
|
| 937 |
+
"output_type": "display_data"
|
| 938 |
+
},
|
| 939 |
+
{
|
| 940 |
+
"data": {
|
| 941 |
+
"text/markdown": [
|
| 942 |
+
"#### Step 1\n",
|
| 943 |
+
"**Product:** `C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1` \n",
|
| 944 |
+
"**Reactants:** `C=CCC1C(=O)OC(=O)C1CC(=C)C[Si](C)(C)C.Cc1cc(C)c(N2CCN(c3c(C)cc(C)cc3C)[C]2=[Ru]([Cl])([Cl])(=[CH]c2ccccc2)[P](C2CCCCC2)(C2CCCCC2)C2CCCCC2)c(C)c1.ClCCl` \n",
|
| 945 |
+
"- Forward likelihood: `0.809` \n",
|
| 946 |
+
"- Arc score: `0.371` \n",
|
| 947 |
+
"- Retro confidence: `0.9503329457084253`"
|
| 948 |
+
],
|
| 949 |
+
"text/plain": [
|
| 950 |
+
"<IPython.core.display.Markdown object>"
|
| 951 |
+
]
|
| 952 |
+
},
|
| 953 |
+
"metadata": {},
|
| 954 |
+
"output_type": "display_data"
|
| 955 |
+
},
|
| 956 |
+
{
|
| 957 |
+
"data": {
|
| 958 |
+
"text/markdown": [
|
| 959 |
+
"## Route 3\n",
|
| 960 |
+
"- **Score:** `0.339`\n",
|
| 961 |
+
"- **Steps:** `1`"
|
| 962 |
+
],
|
| 963 |
+
"text/plain": [
|
| 964 |
+
"<IPython.core.display.Markdown object>"
|
| 965 |
+
]
|
| 966 |
+
},
|
| 967 |
+
"metadata": {},
|
| 968 |
+
"output_type": "display_data"
|
| 969 |
+
},
|
| 970 |
+
{
|
| 971 |
+
"data": {
|
| 972 |
+
"text/markdown": [
|
| 973 |
+
"**Remaining molecules:** `ClCCl, C=CCC1C(=O)OC(=O)C1CC(=C[Si](C)(C)C)C[Si](C)(C)C, Cc1cc(C)c(N2CCN(c3c(C)cc(C)cc3C)[C]2=[Ru]([Cl])([Cl])(=[CH]c2ccccc2)[P](C2CCCCC2)(C2CCCCC2)C2CCCCC2)c(C)c1`"
|
| 974 |
+
],
|
| 975 |
+
"text/plain": [
|
| 976 |
+
"<IPython.core.display.Markdown object>"
|
| 977 |
+
]
|
| 978 |
+
},
|
| 979 |
+
"metadata": {},
|
| 980 |
+
"output_type": "display_data"
|
| 981 |
+
},
|
| 982 |
+
{
|
| 983 |
+
"data": {
|
| 984 |
+
"text/markdown": [
|
| 985 |
+
"#### Step 1\n",
|
| 986 |
+
"**Product:** `C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1` \n",
|
| 987 |
+
"**Reactants:** `C=CCC1C(=O)OC(=O)C1CC(=C[Si](C)(C)C)C[Si](C)(C)C.Cc1cc(C)c(N2CCN(c3c(C)cc(C)cc3C)[C]2=[Ru]([Cl])([Cl])(=[CH]c2ccccc2)[P](C2CCCCC2)(C2CCCCC2)C2CCCCC2)c(C)c1.ClCCl` \n",
|
| 988 |
+
"- Forward likelihood: `0.807` \n",
|
| 989 |
+
"- Arc score: `0.339` \n",
|
| 990 |
+
"- Retro confidence: `0.9455194125006082`"
|
| 991 |
+
],
|
| 992 |
+
"text/plain": [
|
| 993 |
+
"<IPython.core.display.Markdown object>"
|
| 994 |
+
]
|
| 995 |
+
},
|
| 996 |
+
"metadata": {},
|
| 997 |
+
"output_type": "display_data"
|
| 998 |
+
},
|
| 999 |
+
{
|
| 1000 |
+
"data": {
|
| 1001 |
+
"text/markdown": [
|
| 1002 |
+
"## Route 4\n",
|
| 1003 |
+
"- **Score:** `0.249`\n",
|
| 1004 |
+
"- **Steps:** `3`"
|
| 1005 |
+
],
|
| 1006 |
+
"text/plain": [
|
| 1007 |
+
"<IPython.core.display.Markdown object>"
|
| 1008 |
+
]
|
| 1009 |
+
},
|
| 1010 |
+
"metadata": {},
|
| 1011 |
+
"output_type": "display_data"
|
| 1012 |
+
},
|
| 1013 |
+
{
|
| 1014 |
+
"data": {
|
| 1015 |
+
"text/markdown": [
|
| 1016 |
+
"**Remaining molecules:** `C1CCOC1, O=C1C=CC(=O)O1, C=CC(=C)O, ClP(Cl)(Cl)(Cl)Cl, Cc1ccccc1, C[Si](C)(C)[CH2][Mg][Cl]`"
|
| 1017 |
+
],
|
| 1018 |
+
"text/plain": [
|
| 1019 |
+
"<IPython.core.display.Markdown object>"
|
| 1020 |
+
]
|
| 1021 |
+
},
|
| 1022 |
+
"metadata": {},
|
| 1023 |
+
"output_type": "display_data"
|
| 1024 |
+
},
|
| 1025 |
+
{
|
| 1026 |
+
"data": {
|
| 1027 |
+
"text/markdown": [
|
| 1028 |
+
"#### Step 1\n",
|
| 1029 |
+
"**Product:** `C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1` \n",
|
| 1030 |
+
"**Reactants:** `C=CC(=C)C[Si](C)(C)C.Cc1ccccc1.O=C1C=CC(=O)O1` \n",
|
| 1031 |
+
"- Forward likelihood: `0.393` \n",
|
| 1032 |
+
"- Arc score: `0.437` \n",
|
| 1033 |
+
"- Retro confidence: `0.9271861522313083`"
|
| 1034 |
+
],
|
| 1035 |
+
"text/plain": [
|
| 1036 |
+
"<IPython.core.display.Markdown object>"
|
| 1037 |
+
]
|
| 1038 |
+
},
|
| 1039 |
+
"metadata": {},
|
| 1040 |
+
"output_type": "display_data"
|
| 1041 |
+
},
|
| 1042 |
+
{
|
| 1043 |
+
"data": {
|
| 1044 |
+
"text/markdown": [
|
| 1045 |
+
"#### Step 2\n",
|
| 1046 |
+
"**Product:** `C=CC(=C)C[Si](C)(C)C` \n",
|
| 1047 |
+
"**Reactants:** `C1CCOC1.C=CC(=C)Cl.C[Si](C)(C)[CH2][Mg][Cl]` \n",
|
| 1048 |
+
"- Forward likelihood: `0.940` \n",
|
| 1049 |
+
"- Arc score: `1.050` \n",
|
| 1050 |
+
"- Retro confidence: `0.8633497322540727`"
|
| 1051 |
+
],
|
| 1052 |
+
"text/plain": [
|
| 1053 |
+
"<IPython.core.display.Markdown object>"
|
| 1054 |
+
]
|
| 1055 |
+
},
|
| 1056 |
+
"metadata": {},
|
| 1057 |
+
"output_type": "display_data"
|
| 1058 |
+
},
|
| 1059 |
+
{
|
| 1060 |
+
"data": {
|
| 1061 |
+
"text/markdown": [
|
| 1062 |
+
"#### Step 3\n",
|
| 1063 |
+
"**Product:** `C=CC(=C)Cl` \n",
|
| 1064 |
+
"**Reactants:** `C=CC(=C)O.ClP(Cl)(Cl)(Cl)Cl` \n",
|
| 1065 |
+
"- Forward likelihood: `0.556` \n",
|
| 1066 |
+
"- Arc score: `0.542` \n",
|
| 1067 |
+
"- Retro confidence: `0.8283158501723433`"
|
| 1068 |
+
],
|
| 1069 |
+
"text/plain": [
|
| 1070 |
+
"<IPython.core.display.Markdown object>"
|
| 1071 |
+
]
|
| 1072 |
+
},
|
| 1073 |
+
"metadata": {},
|
| 1074 |
+
"output_type": "display_data"
|
| 1075 |
+
},
|
| 1076 |
+
{
|
| 1077 |
+
"data": {
|
| 1078 |
+
"text/markdown": [
|
| 1079 |
+
"## Route 5\n",
|
| 1080 |
+
"- **Score:** `0.242`\n",
|
| 1081 |
+
"- **Steps:** `3`"
|
| 1082 |
+
],
|
| 1083 |
+
"text/plain": [
|
| 1084 |
+
"<IPython.core.display.Markdown object>"
|
| 1085 |
+
]
|
| 1086 |
+
},
|
| 1087 |
+
"metadata": {},
|
| 1088 |
+
"output_type": "display_data"
|
| 1089 |
+
},
|
| 1090 |
+
{
|
| 1091 |
+
"data": {
|
| 1092 |
+
"text/markdown": [
|
| 1093 |
+
"**Remaining molecules:** `O=C1C=CC(=O)O1, [NH4+], C=CC(=C)O, ClP(Cl)(Cl)(Cl)Cl, [Cl-], Cc1ccccc1, C[Si](C)(C)[CH2][Mg][Cl], C1CCOC1, O`"
|
| 1094 |
+
],
|
| 1095 |
+
"text/plain": [
|
| 1096 |
+
"<IPython.core.display.Markdown object>"
|
| 1097 |
+
]
|
| 1098 |
+
},
|
| 1099 |
+
"metadata": {},
|
| 1100 |
+
"output_type": "display_data"
|
| 1101 |
+
},
|
| 1102 |
+
{
|
| 1103 |
+
"data": {
|
| 1104 |
+
"text/markdown": [
|
| 1105 |
+
"#### Step 1\n",
|
| 1106 |
+
"**Product:** `C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1` \n",
|
| 1107 |
+
"**Reactants:** `C=CC(=C)C[Si](C)(C)C.Cc1ccccc1.O=C1C=CC(=O)O1` \n",
|
| 1108 |
+
"- Forward likelihood: `0.393` \n",
|
| 1109 |
+
"- Arc score: `0.437` \n",
|
| 1110 |
+
"- Retro confidence: `0.9271861522313083`"
|
| 1111 |
+
],
|
| 1112 |
+
"text/plain": [
|
| 1113 |
+
"<IPython.core.display.Markdown object>"
|
| 1114 |
+
]
|
| 1115 |
+
},
|
| 1116 |
+
"metadata": {},
|
| 1117 |
+
"output_type": "display_data"
|
| 1118 |
+
},
|
| 1119 |
+
{
|
| 1120 |
+
"data": {
|
| 1121 |
+
"text/markdown": [
|
| 1122 |
+
"#### Step 2\n",
|
| 1123 |
+
"**Product:** `C=CC(=C)C[Si](C)(C)C` \n",
|
| 1124 |
+
"**Reactants:** `C1CCOC1.C=CC(=C)Cl.C[Si](C)(C)[CH2][Mg][Cl].O.[Cl-].[NH4+]` \n",
|
| 1125 |
+
"- Forward likelihood: `0.917` \n",
|
| 1126 |
+
"- Arc score: `1.025` \n",
|
| 1127 |
+
"- Retro confidence: `0.8341045215463807`"
|
| 1128 |
+
],
|
| 1129 |
+
"text/plain": [
|
| 1130 |
+
"<IPython.core.display.Markdown object>"
|
| 1131 |
+
]
|
| 1132 |
+
},
|
| 1133 |
+
"metadata": {},
|
| 1134 |
+
"output_type": "display_data"
|
| 1135 |
+
},
|
| 1136 |
+
{
|
| 1137 |
+
"data": {
|
| 1138 |
+
"text/markdown": [
|
| 1139 |
+
"#### Step 3\n",
|
| 1140 |
+
"**Product:** `C=CC(=C)Cl` \n",
|
| 1141 |
+
"**Reactants:** `C=CC(=C)O.ClP(Cl)(Cl)(Cl)Cl` \n",
|
| 1142 |
+
"- Forward likelihood: `0.556` \n",
|
| 1143 |
+
"- Arc score: `0.542` \n",
|
| 1144 |
+
"- Retro confidence: `0.8283158501723433`"
|
| 1145 |
+
],
|
| 1146 |
+
"text/plain": [
|
| 1147 |
+
"<IPython.core.display.Markdown object>"
|
| 1148 |
+
]
|
| 1149 |
+
},
|
| 1150 |
+
"metadata": {},
|
| 1151 |
+
"output_type": "display_data"
|
| 1152 |
+
},
|
| 1153 |
+
{
|
| 1154 |
+
"data": {
|
| 1155 |
+
"text/markdown": [
|
| 1156 |
+
"## Route 6\n",
|
| 1157 |
+
"- **Score:** `0.233`\n",
|
| 1158 |
+
"- **Steps:** `3`"
|
| 1159 |
+
],
|
| 1160 |
+
"text/plain": [
|
| 1161 |
+
"<IPython.core.display.Markdown object>"
|
| 1162 |
+
]
|
| 1163 |
+
},
|
| 1164 |
+
"metadata": {},
|
| 1165 |
+
"output_type": "display_data"
|
| 1166 |
+
},
|
| 1167 |
+
{
|
| 1168 |
+
"data": {
|
| 1169 |
+
"text/markdown": [
|
| 1170 |
+
"**Remaining molecules:** `O=C1C=CC(=O)O1, C=CC(=C)O, CCOCC, ClP(Cl)(Cl)(Cl)Cl, Cc1ccccc1, C[Si](C)(C)[CH2][Mg][Cl], C1CCOC1, Cl, O`"
|
| 1171 |
+
],
|
| 1172 |
+
"text/plain": [
|
| 1173 |
+
"<IPython.core.display.Markdown object>"
|
| 1174 |
+
]
|
| 1175 |
+
},
|
| 1176 |
+
"metadata": {},
|
| 1177 |
+
"output_type": "display_data"
|
| 1178 |
+
},
|
| 1179 |
+
{
|
| 1180 |
+
"data": {
|
| 1181 |
+
"text/markdown": [
|
| 1182 |
+
"#### Step 1\n",
|
| 1183 |
+
"**Product:** `C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1` \n",
|
| 1184 |
+
"**Reactants:** `C=CC(=C)C[Si](C)(C)C.Cc1ccccc1.O=C1C=CC(=O)O1` \n",
|
| 1185 |
+
"- Forward likelihood: `0.393` \n",
|
| 1186 |
+
"- Arc score: `0.437` \n",
|
| 1187 |
+
"- Retro confidence: `0.9271861522313083`"
|
| 1188 |
+
],
|
| 1189 |
+
"text/plain": [
|
| 1190 |
+
"<IPython.core.display.Markdown object>"
|
| 1191 |
+
]
|
| 1192 |
+
},
|
| 1193 |
+
"metadata": {},
|
| 1194 |
+
"output_type": "display_data"
|
| 1195 |
+
},
|
| 1196 |
+
{
|
| 1197 |
+
"data": {
|
| 1198 |
+
"text/markdown": [
|
| 1199 |
+
"#### Step 2\n",
|
| 1200 |
+
"**Product:** `C=CC(=C)C[Si](C)(C)C` \n",
|
| 1201 |
+
"**Reactants:** `C1CCOC1.C=CC(=C)Cl.CCOCC.C[Si](C)(C)[CH2][Mg][Cl].Cl.O` \n",
|
| 1202 |
+
"- Forward likelihood: `0.882` \n",
|
| 1203 |
+
"- Arc score: `0.986` \n",
|
| 1204 |
+
"- Retro confidence: `0.8303334989016697`"
|
| 1205 |
+
],
|
| 1206 |
+
"text/plain": [
|
| 1207 |
+
"<IPython.core.display.Markdown object>"
|
| 1208 |
+
]
|
| 1209 |
+
},
|
| 1210 |
+
"metadata": {},
|
| 1211 |
+
"output_type": "display_data"
|
| 1212 |
+
},
|
| 1213 |
+
{
|
| 1214 |
+
"data": {
|
| 1215 |
+
"text/markdown": [
|
| 1216 |
+
"#### Step 3\n",
|
| 1217 |
+
"**Product:** `C=CC(=C)Cl` \n",
|
| 1218 |
+
"**Reactants:** `C=CC(=C)O.ClP(Cl)(Cl)(Cl)Cl` \n",
|
| 1219 |
+
"- Forward likelihood: `0.556` \n",
|
| 1220 |
+
"- Arc score: `0.542` \n",
|
| 1221 |
+
"- Retro confidence: `0.8283158501723433`"
|
| 1222 |
+
],
|
| 1223 |
+
"text/plain": [
|
| 1224 |
+
"<IPython.core.display.Markdown object>"
|
| 1225 |
+
]
|
| 1226 |
+
},
|
| 1227 |
+
"metadata": {},
|
| 1228 |
+
"output_type": "display_data"
|
| 1229 |
+
},
|
| 1230 |
+
{
|
| 1231 |
+
"data": {
|
| 1232 |
+
"text/markdown": [
|
| 1233 |
+
"## Route 7\n",
|
| 1234 |
+
"- **Score:** `0.218`\n",
|
| 1235 |
+
"- **Steps:** `3`"
|
| 1236 |
+
],
|
| 1237 |
+
"text/plain": [
|
| 1238 |
+
"<IPython.core.display.Markdown object>"
|
| 1239 |
+
]
|
| 1240 |
+
},
|
| 1241 |
+
"metadata": {},
|
| 1242 |
+
"output_type": "display_data"
|
| 1243 |
+
},
|
| 1244 |
+
{
|
| 1245 |
+
"data": {
|
| 1246 |
+
"text/markdown": [
|
| 1247 |
+
"**Remaining molecules:** `O=C1C=CC(=O)O1, [NH4+], C=CC(=C)O, CCOCC, [Cl-], ClP(Cl)(Cl)(Cl)Cl, Cc1ccccc1, C[Si](C)(C)[CH2][Mg][Cl], C1CCOC1, O, [Mg]`"
|
| 1248 |
+
],
|
| 1249 |
+
"text/plain": [
|
| 1250 |
+
"<IPython.core.display.Markdown object>"
|
| 1251 |
+
]
|
| 1252 |
+
},
|
| 1253 |
+
"metadata": {},
|
| 1254 |
+
"output_type": "display_data"
|
| 1255 |
+
},
|
| 1256 |
+
{
|
| 1257 |
+
"data": {
|
| 1258 |
+
"text/markdown": [
|
| 1259 |
+
"#### Step 1\n",
|
| 1260 |
+
"**Product:** `C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1` \n",
|
| 1261 |
+
"**Reactants:** `C=CC(=C)C[Si](C)(C)C.Cc1ccccc1.O=C1C=CC(=O)O1` \n",
|
| 1262 |
+
"- Forward likelihood: `0.393` \n",
|
| 1263 |
+
"- Arc score: `0.437` \n",
|
| 1264 |
+
"- Retro confidence: `0.9271861522313083`"
|
| 1265 |
+
],
|
| 1266 |
+
"text/plain": [
|
| 1267 |
+
"<IPython.core.display.Markdown object>"
|
| 1268 |
+
]
|
| 1269 |
+
},
|
| 1270 |
+
"metadata": {},
|
| 1271 |
+
"output_type": "display_data"
|
| 1272 |
+
},
|
| 1273 |
+
{
|
| 1274 |
+
"data": {
|
| 1275 |
+
"text/markdown": [
|
| 1276 |
+
"#### Step 2\n",
|
| 1277 |
+
"**Product:** `C=CC(=C)C[Si](C)(C)C` \n",
|
| 1278 |
+
"**Reactants:** `C1CCOC1.C=CC(=C)Cl.CCOCC.C[Si](C)(C)[CH2][Mg][Cl].O.[Cl-].[Mg].[NH4+]` \n",
|
| 1279 |
+
"- Forward likelihood: `0.862` \n",
|
| 1280 |
+
"- Arc score: `0.921` \n",
|
| 1281 |
+
"- Retro confidence: `0.8301065237221663`"
|
| 1282 |
+
],
|
| 1283 |
+
"text/plain": [
|
| 1284 |
+
"<IPython.core.display.Markdown object>"
|
| 1285 |
+
]
|
| 1286 |
+
},
|
| 1287 |
+
"metadata": {},
|
| 1288 |
+
"output_type": "display_data"
|
| 1289 |
+
},
|
| 1290 |
+
{
|
| 1291 |
+
"data": {
|
| 1292 |
+
"text/markdown": [
|
| 1293 |
+
"#### Step 3\n",
|
| 1294 |
+
"**Product:** `C=CC(=C)Cl` \n",
|
| 1295 |
+
"**Reactants:** `C=CC(=C)O.ClP(Cl)(Cl)(Cl)Cl` \n",
|
| 1296 |
+
"- Forward likelihood: `0.556` \n",
|
| 1297 |
+
"- Arc score: `0.542` \n",
|
| 1298 |
+
"- Retro confidence: `0.8283158501723433`"
|
| 1299 |
+
],
|
| 1300 |
+
"text/plain": [
|
| 1301 |
+
"<IPython.core.display.Markdown object>"
|
| 1302 |
+
]
|
| 1303 |
+
},
|
| 1304 |
+
"metadata": {},
|
| 1305 |
+
"output_type": "display_data"
|
| 1306 |
+
},
|
| 1307 |
+
{
|
| 1308 |
+
"data": {
|
| 1309 |
+
"text/markdown": [
|
| 1310 |
+
"## Route 8\n",
|
| 1311 |
+
"- **Score:** `0.180`\n",
|
| 1312 |
+
"- **Steps:** `3`"
|
| 1313 |
+
],
|
| 1314 |
+
"text/plain": [
|
| 1315 |
+
"<IPython.core.display.Markdown object>"
|
| 1316 |
+
]
|
| 1317 |
+
},
|
| 1318 |
+
"metadata": {},
|
| 1319 |
+
"output_type": "display_data"
|
| 1320 |
+
},
|
| 1321 |
+
{
|
| 1322 |
+
"data": {
|
| 1323 |
+
"text/markdown": [
|
| 1324 |
+
"**Remaining molecules:** `C1CCOC1, O=C1C=CC(=O)O1, C=CC(=C)O, O=S(Cl)Cl, Cc1ccccc1, C[Si](C)(C)[CH2][Mg][Cl]`"
|
| 1325 |
+
],
|
| 1326 |
+
"text/plain": [
|
| 1327 |
+
"<IPython.core.display.Markdown object>"
|
| 1328 |
+
]
|
| 1329 |
+
},
|
| 1330 |
+
"metadata": {},
|
| 1331 |
+
"output_type": "display_data"
|
| 1332 |
+
},
|
| 1333 |
+
{
|
| 1334 |
+
"data": {
|
| 1335 |
+
"text/markdown": [
|
| 1336 |
+
"#### Step 1\n",
|
| 1337 |
+
"**Product:** `C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1` \n",
|
| 1338 |
+
"**Reactants:** `C=CC(=C)C[Si](C)(C)C.Cc1ccccc1.O=C1C=CC(=O)O1` \n",
|
| 1339 |
+
"- Forward likelihood: `0.393` \n",
|
| 1340 |
+
"- Arc score: `0.437` \n",
|
| 1341 |
+
"- Retro confidence: `0.9271861522313083`"
|
| 1342 |
+
],
|
| 1343 |
+
"text/plain": [
|
| 1344 |
+
"<IPython.core.display.Markdown object>"
|
| 1345 |
+
]
|
| 1346 |
+
},
|
| 1347 |
+
"metadata": {},
|
| 1348 |
+
"output_type": "display_data"
|
| 1349 |
+
},
|
| 1350 |
+
{
|
| 1351 |
+
"data": {
|
| 1352 |
+
"text/markdown": [
|
| 1353 |
+
"#### Step 2\n",
|
| 1354 |
+
"**Product:** `C=CC(=C)C[Si](C)(C)C` \n",
|
| 1355 |
+
"**Reactants:** `C1CCOC1.C=CC(=C)Cl.C[Si](C)(C)[CH2][Mg][Cl]` \n",
|
| 1356 |
+
"- Forward likelihood: `0.940` \n",
|
| 1357 |
+
"- Arc score: `1.050` \n",
|
| 1358 |
+
"- Retro confidence: `0.8633497322540727`"
|
| 1359 |
+
],
|
| 1360 |
+
"text/plain": [
|
| 1361 |
+
"<IPython.core.display.Markdown object>"
|
| 1362 |
+
]
|
| 1363 |
+
},
|
| 1364 |
+
"metadata": {},
|
| 1365 |
+
"output_type": "display_data"
|
| 1366 |
+
},
|
| 1367 |
+
{
|
| 1368 |
+
"data": {
|
| 1369 |
+
"text/markdown": [
|
| 1370 |
+
"#### Step 3\n",
|
| 1371 |
+
"**Product:** `C=CC(=C)Cl` \n",
|
| 1372 |
+
"**Reactants:** `C=CC(=C)O.O=S(Cl)Cl` \n",
|
| 1373 |
+
"- Forward likelihood: `0.404` \n",
|
| 1374 |
+
"- Arc score: `0.393` \n",
|
| 1375 |
+
"- Retro confidence: `0.8269444351225532`"
|
| 1376 |
+
],
|
| 1377 |
+
"text/plain": [
|
| 1378 |
+
"<IPython.core.display.Markdown object>"
|
| 1379 |
+
]
|
| 1380 |
+
},
|
| 1381 |
+
"metadata": {},
|
| 1382 |
+
"output_type": "display_data"
|
| 1383 |
+
},
|
| 1384 |
+
{
|
| 1385 |
+
"data": {
|
| 1386 |
+
"text/markdown": [
|
| 1387 |
+
"## Route 9\n",
|
| 1388 |
+
"- **Score:** `0.180`\n",
|
| 1389 |
+
"- **Steps:** `1`"
|
| 1390 |
+
],
|
| 1391 |
+
"text/plain": [
|
| 1392 |
+
"<IPython.core.display.Markdown object>"
|
| 1393 |
+
]
|
| 1394 |
+
},
|
| 1395 |
+
"metadata": {},
|
| 1396 |
+
"output_type": "display_data"
|
| 1397 |
+
},
|
| 1398 |
+
{
|
| 1399 |
+
"data": {
|
| 1400 |
+
"text/markdown": [
|
| 1401 |
+
"**Remaining molecules:** `O=C1OC(=O)C2CC=CCC12, Cc1cc(C)c(N2CCN(c3c(C)cc(C)cc3C)[C]2=[Ru]([Cl])([Cl])=[CH]c2ccccc2)c(C)c1, ClCCl, C=CC(=C)C[Si](C)(C)C, C1CCC(P(C2CCCCC2)C2CCCCC2)CC1, N#N`"
|
| 1402 |
+
],
|
| 1403 |
+
"text/plain": [
|
| 1404 |
+
"<IPython.core.display.Markdown object>"
|
| 1405 |
+
]
|
| 1406 |
+
},
|
| 1407 |
+
"metadata": {},
|
| 1408 |
+
"output_type": "display_data"
|
| 1409 |
+
},
|
| 1410 |
+
{
|
| 1411 |
+
"data": {
|
| 1412 |
+
"text/markdown": [
|
| 1413 |
+
"#### Step 1\n",
|
| 1414 |
+
"**Product:** `C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1` \n",
|
| 1415 |
+
"**Reactants:** `C1CCC(P(C2CCCCC2)C2CCCCC2)CC1.C=CC(=C)C[Si](C)(C)C.Cc1cc(C)c(N2CCN(c3c(C)cc(C)cc3C)[C]2=[Ru]([Cl])([Cl])=[CH]c2ccccc2)c(C)c1.ClCCl.N#N.O=C1OC(=O)C2CC=CCC12` \n",
|
| 1416 |
+
"- Forward likelihood: `0.395` \n",
|
| 1417 |
+
"- Arc score: `0.180` \n",
|
| 1418 |
+
"- Retro confidence: `0.9290369156214755`"
|
| 1419 |
+
],
|
| 1420 |
+
"text/plain": [
|
| 1421 |
+
"<IPython.core.display.Markdown object>"
|
| 1422 |
+
]
|
| 1423 |
+
},
|
| 1424 |
+
"metadata": {},
|
| 1425 |
+
"output_type": "display_data"
|
| 1426 |
+
},
|
| 1427 |
+
{
|
| 1428 |
+
"data": {
|
| 1429 |
+
"text/markdown": [
|
| 1430 |
+
"## Route 10\n",
|
| 1431 |
+
"- **Score:** `0.176`\n",
|
| 1432 |
+
"- **Steps:** `3`"
|
| 1433 |
+
],
|
| 1434 |
+
"text/plain": [
|
| 1435 |
+
"<IPython.core.display.Markdown object>"
|
| 1436 |
+
]
|
| 1437 |
+
},
|
| 1438 |
+
"metadata": {},
|
| 1439 |
+
"output_type": "display_data"
|
| 1440 |
+
},
|
| 1441 |
+
{
|
| 1442 |
+
"data": {
|
| 1443 |
+
"text/markdown": [
|
| 1444 |
+
"**Remaining molecules:** `O=C1C=CC(=O)O1, [NH4+], C=CC(=C)O, [Cl-], O=S(Cl)Cl, Cc1ccccc1, C[Si](C)(C)[CH2][Mg][Cl], C1CCOC1, O`"
|
| 1445 |
+
],
|
| 1446 |
+
"text/plain": [
|
| 1447 |
+
"<IPython.core.display.Markdown object>"
|
| 1448 |
+
]
|
| 1449 |
+
},
|
| 1450 |
+
"metadata": {},
|
| 1451 |
+
"output_type": "display_data"
|
| 1452 |
+
},
|
| 1453 |
+
{
|
| 1454 |
+
"data": {
|
| 1455 |
+
"text/markdown": [
|
| 1456 |
+
"#### Step 1\n",
|
| 1457 |
+
"**Product:** `C[Si](C)(C)CC1=CCC2C(=O)OC(=O)C2C1` \n",
|
| 1458 |
+
"**Reactants:** `C=CC(=C)C[Si](C)(C)C.Cc1ccccc1.O=C1C=CC(=O)O1` \n",
|
| 1459 |
+
"- Forward likelihood: `0.393` \n",
|
| 1460 |
+
"- Arc score: `0.437` \n",
|
| 1461 |
+
"- Retro confidence: `0.9271861522313083`"
|
| 1462 |
+
],
|
| 1463 |
+
"text/plain": [
|
| 1464 |
+
"<IPython.core.display.Markdown object>"
|
| 1465 |
+
]
|
| 1466 |
+
},
|
| 1467 |
+
"metadata": {},
|
| 1468 |
+
"output_type": "display_data"
|
| 1469 |
+
},
|
| 1470 |
+
{
|
| 1471 |
+
"data": {
|
| 1472 |
+
"text/markdown": [
|
| 1473 |
+
"#### Step 2\n",
|
| 1474 |
+
"**Product:** `C=CC(=C)C[Si](C)(C)C` \n",
|
| 1475 |
+
"**Reactants:** `C1CCOC1.C=CC(=C)Cl.C[Si](C)(C)[CH2][Mg][Cl].O.[Cl-].[NH4+]` \n",
|
| 1476 |
+
"- Forward likelihood: `0.917` \n",
|
| 1477 |
+
"- Arc score: `1.025` \n",
|
| 1478 |
+
"- Retro confidence: `0.8341045215463807`"
|
| 1479 |
+
],
|
| 1480 |
+
"text/plain": [
|
| 1481 |
+
"<IPython.core.display.Markdown object>"
|
| 1482 |
+
]
|
| 1483 |
+
},
|
| 1484 |
+
"metadata": {},
|
| 1485 |
+
"output_type": "display_data"
|
| 1486 |
+
},
|
| 1487 |
+
{
|
| 1488 |
+
"data": {
|
| 1489 |
+
"text/markdown": [
|
| 1490 |
+
"#### Step 3\n",
|
| 1491 |
+
"**Product:** `C=CC(=C)Cl` \n",
|
| 1492 |
+
"**Reactants:** `C=CC(=C)O.O=S(Cl)Cl` \n",
|
| 1493 |
+
"- Forward likelihood: `0.404` \n",
|
| 1494 |
+
"- Arc score: `0.393` \n",
|
| 1495 |
+
"- Retro confidence: `0.8269444351225532`"
|
| 1496 |
+
],
|
| 1497 |
+
"text/plain": [
|
| 1498 |
+
"<IPython.core.display.Markdown object>"
|
| 1499 |
+
]
|
| 1500 |
+
},
|
| 1501 |
+
"metadata": {},
|
| 1502 |
+
"output_type": "display_data"
|
| 1503 |
+
}
|
| 1504 |
+
],
|
| 1505 |
+
"source": [
|
| 1506 |
+
"# Textual retrosynthesis tree representation\n",
|
| 1507 |
+
"for i, route in enumerate(response[\"result\"]):\n",
|
| 1508 |
+
" display_route(route, i)"
|
| 1509 |
+
]
|
| 1510 |
+
},
|
| 1511 |
+
{
|
| 1512 |
+
"cell_type": "code",
|
| 1513 |
+
"execution_count": 13,
|
| 1514 |
+
"id": "8491bcb4-b096-4277-9244-8b59b4499fe2",
|
| 1515 |
+
"metadata": {},
|
| 1516 |
+
"outputs": [
|
| 1517 |
+
{
|
| 1518 |
+
"data": {
|
| 1519 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 1520 |
+
"model_id": "37b74ae49d7748b7acbc0e82ad6b285d",
|
| 1521 |
+
"version_major": 2,
|
| 1522 |
+
"version_minor": 0
|
| 1523 |
+
},
|
| 1524 |
+
"text/plain": [
|
| 1525 |
+
"interactive(children=(Dropdown(description='Route', options=(('Route 1 | score=0.706', 0), ('Route 2 | score=0…"
|
| 1526 |
+
]
|
| 1527 |
+
},
|
| 1528 |
+
"metadata": {},
|
| 1529 |
+
"output_type": "display_data"
|
| 1530 |
+
}
|
| 1531 |
+
],
|
| 1532 |
+
"source": [
|
| 1533 |
+
"# Graphical retrosynthesis tree representation with route selection\n",
|
| 1534 |
+
"tree_route_selector(response[\"result\"])"
|
| 1535 |
+
]
|
| 1536 |
+
},
|
| 1537 |
+
{
|
| 1538 |
+
"cell_type": "code",
|
| 1539 |
+
"execution_count": null,
|
| 1540 |
+
"id": "5f7af0df-b7a9-4e93-ac40-27fc33d0a103",
|
| 1541 |
+
"metadata": {},
|
| 1542 |
+
"outputs": [],
|
| 1543 |
+
"source": []
|
| 1544 |
+
}
|
| 1545 |
+
],
|
| 1546 |
+
"metadata": {
|
| 1547 |
+
"kernelspec": {
|
| 1548 |
+
"display_name": "Python 3 (ipykernel)",
|
| 1549 |
+
"language": "python",
|
| 1550 |
+
"name": "python3"
|
| 1551 |
+
},
|
| 1552 |
+
"language_info": {
|
| 1553 |
+
"codemirror_mode": {
|
| 1554 |
+
"name": "ipython",
|
| 1555 |
+
"version": 3
|
| 1556 |
+
},
|
| 1557 |
+
"file_extension": ".py",
|
| 1558 |
+
"mimetype": "text/x-python",
|
| 1559 |
+
"name": "python",
|
| 1560 |
+
"nbconvert_exporter": "python",
|
| 1561 |
+
"pygments_lexer": "ipython3",
|
| 1562 |
+
"version": "3.13.12"
|
| 1563 |
+
}
|
| 1564 |
+
},
|
| 1565 |
+
"nbformat": 4,
|
| 1566 |
+
"nbformat_minor": 5
|
| 1567 |
+
}
|
jupyter/requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
celery[redis]
|
| 2 |
+
ipykernel
|
| 3 |
+
ipywidgets
|
| 4 |
+
matplotlib
|
| 5 |
+
notebook
|
| 6 |
+
pandas
|
| 7 |
+
rdkit
|
mcp/Dockerfile
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM public.ecr.aws/docker/library/python:3.13-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
COPY requirements.txt .
|
| 6 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 7 |
+
|
| 8 |
+
COPY server.py start.sh .
|
| 9 |
+
RUN chmod +x start.sh
|
| 10 |
+
|
| 11 |
+
CMD ["./start.sh"]
|
mcp/requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
celery
|
| 2 |
+
mcp
|
| 3 |
+
mcpo
|
| 4 |
+
redis
|
mcp/server.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import asyncio
|
| 3 |
+
from typing import Optional
|
| 4 |
+
from mcp.server.fastmcp import FastMCP
|
| 5 |
+
from celery import Celery
|
| 6 |
+
from celery.exceptions import TimeoutError
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
CELERY_BROKER_URL = os.getenv("CELERY_BROKER_URL", "amqp://ubuntu:ubuntu@broker:5672//")
|
| 10 |
+
CELERY_RESULT_BACKEND = os.getenv("CELERY_RESULT_BACKEND", "redis://:ubuntu@redis:6379/0")
|
| 11 |
+
|
| 12 |
+
app = Celery("mcp-client", broker=CELERY_BROKER_URL, backend=CELERY_RESULT_BACKEND)
|
| 13 |
+
|
| 14 |
+
mcp = FastMCP("rxn-mcp-server", json_response=False)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
async def wait_for_celery_result(task, timeout: int = 300):
|
| 18 |
+
def _get():
|
| 19 |
+
return task.get(timeout=timeout)
|
| 20 |
+
|
| 21 |
+
return await asyncio.to_thread(_get)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
@mcp.tool()
|
| 25 |
+
async def product_prediction(
|
| 26 |
+
reactants_list: list[str],
|
| 27 |
+
topn: int = 3,
|
| 28 |
+
num_beams: int = 3,
|
| 29 |
+
device: Optional[str] = None,
|
| 30 |
+
ckpt_forward = "Pistachio2025Q2-Forward",
|
| 31 |
+
vocab = "Pistachio2025Q2",
|
| 32 |
+
):
|
| 33 |
+
"""
|
| 34 |
+
Gets the product prediction for a batch of reactants SMILES.
|
| 35 |
+
|
| 36 |
+
Args:
|
| 37 |
+
reactants_list (list[str]): List of reactants SMILES.
|
| 38 |
+
topn (int): number of predictions per reactant (Defaults to 3).
|
| 39 |
+
num_beams (int): beams used for prediction (num_beams >= topn) (Defaults to 3).
|
| 40 |
+
device (Optional str): device used for predicting, either "cuda" or "cpu", None defaults to cuda if available.
|
| 41 |
+
ckpt_forward (Optional str): name of the forward model without extension. Should exist inside the "models/forward" directory and be a .ckpt file.
|
| 42 |
+
vocab (Optional str): name of the vocab file without extension. Should exist inside the "vocab" directory and be a .txt file.
|
| 43 |
+
"""
|
| 44 |
+
try:
|
| 45 |
+
kwargs = {
|
| 46 |
+
"topn": topn,
|
| 47 |
+
"num_beams": num_beams,
|
| 48 |
+
"device": device,
|
| 49 |
+
"ckpt_forward": ckpt_forward,
|
| 50 |
+
"vocab": vocab,
|
| 51 |
+
}
|
| 52 |
+
task = app.send_task(
|
| 53 |
+
"tasks.product_prediction",
|
| 54 |
+
[reactants_list],
|
| 55 |
+
kwargs=kwargs,
|
| 56 |
+
queue="product_prediction",
|
| 57 |
+
)
|
| 58 |
+
result = await wait_for_celery_result(task, timeout=600)
|
| 59 |
+
result["status"] = "success"
|
| 60 |
+
return result
|
| 61 |
+
except TimeoutError:
|
| 62 |
+
return {
|
| 63 |
+
"status": "timeout",
|
| 64 |
+
"message": "Computation exceeded time limit",
|
| 65 |
+
}
|
| 66 |
+
except Exception as e:
|
| 67 |
+
return {
|
| 68 |
+
"status": "error",
|
| 69 |
+
"message": str(e),
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
@mcp.tool()
|
| 74 |
+
async def retro_prediction(
|
| 75 |
+
product: str,
|
| 76 |
+
topn: int = 15,
|
| 77 |
+
num_beams: int = 15,
|
| 78 |
+
fap: float = 0.6,
|
| 79 |
+
fld: float = 0.2,
|
| 80 |
+
device: Optional[str] = None,
|
| 81 |
+
ckpt_forward = "Pistachio2025Q2-Forward",
|
| 82 |
+
ckpt_retro = "Pistachio2025Q2-Retro",
|
| 83 |
+
vocab = "Pistachio2025Q2",
|
| 84 |
+
):
|
| 85 |
+
"""
|
| 86 |
+
Gets the retrosynthesis prediction for a product SMILES.
|
| 87 |
+
|
| 88 |
+
Args:
|
| 89 |
+
product (str): List of reactants SMILES.
|
| 90 |
+
topn (int): number of retrosynthesis predictions (Defaults to 15).
|
| 91 |
+
num_beams (int): beams used for prediction (num_beams >= topn) (Defaults to 15).
|
| 92 |
+
fap (float): forward acceptance level (Defaults to 0.6).
|
| 93 |
+
fld (float): forward likelihood delta (Defaults to 0.2).
|
| 94 |
+
device (Optional str): device used for predicting, either "cuda" or "cpu", None defaults to cuda if available.
|
| 95 |
+
ckpt_forward (Optional str): name of the forward model without extension. Should exist inside the "models/forward" directory and be a .ckpt file.
|
| 96 |
+
ckpt_retro (Optional str): name of the retrosynthesis model without extension. Should exist inside the "models/retrosynthesis" directory and be a .ckpt file.
|
| 97 |
+
vocab (Optional str): name of the vocab file without extension. Should exist inside the "vocab" directory and be a .txt file.
|
| 98 |
+
"""
|
| 99 |
+
try:
|
| 100 |
+
kwargs = {
|
| 101 |
+
"topn": topn,
|
| 102 |
+
"num_beams": num_beams,
|
| 103 |
+
"fap": fap,
|
| 104 |
+
"fld": fld,
|
| 105 |
+
"device": device,
|
| 106 |
+
"ckpt_forward": ckpt_forward,
|
| 107 |
+
"ckpt_retro": ckpt_retro,
|
| 108 |
+
"vocab": vocab,
|
| 109 |
+
}
|
| 110 |
+
task = app.send_task(
|
| 111 |
+
"tasks.retro_prediction",
|
| 112 |
+
[product],
|
| 113 |
+
kwargs=kwargs,
|
| 114 |
+
queue="retro_prediction",
|
| 115 |
+
)
|
| 116 |
+
result = await wait_for_celery_result(task, timeout=600)
|
| 117 |
+
result["status"] = "success"
|
| 118 |
+
return result
|
| 119 |
+
except TimeoutError:
|
| 120 |
+
return {
|
| 121 |
+
"status": "timeout",
|
| 122 |
+
"message": "Computation exceeded time limit",
|
| 123 |
+
}
|
| 124 |
+
except Exception as e:
|
| 125 |
+
return {
|
| 126 |
+
"status": "error",
|
| 127 |
+
"message": str(e),
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
@mcp.tool()
|
| 132 |
+
async def retro_tree_prediction(
|
| 133 |
+
product: str,
|
| 134 |
+
topn: int = 15,
|
| 135 |
+
num_beams: int = 15,
|
| 136 |
+
fap: float = 0.6,
|
| 137 |
+
fld: float = 0.2,
|
| 138 |
+
max_depth: int = 3,
|
| 139 |
+
beam_width: int = 5,
|
| 140 |
+
device: Optional[str] = None,
|
| 141 |
+
ckpt_forward = "Pistachio2025Q2-Forward",
|
| 142 |
+
ckpt_retro = "Pistachio2025Q2-Retro",
|
| 143 |
+
vocab = "Pistachio2025Q2",
|
| 144 |
+
):
|
| 145 |
+
"""
|
| 146 |
+
Gets the full retrosynthesis tree for a product SMILES.
|
| 147 |
+
|
| 148 |
+
Args:
|
| 149 |
+
product (str): List of reactants SMILES.
|
| 150 |
+
topn (int): number of retrosynthesis predictions (Defaults to 15).
|
| 151 |
+
num_beams (int): beams used for prediction (num_beams >= topn) (Defaults to 15).
|
| 152 |
+
fap (float): forward acceptance level (Defaults to 0.6).
|
| 153 |
+
fld (float): forward likelihood delta (Defaults to 0.2).
|
| 154 |
+
max_depth (int): maximum number of retrosynthesis expansions in the tree (Defaults to 3).
|
| 155 |
+
beam_width (int): number of molecules chosen for expansion in each step of the tree (Defaults to 5).
|
| 156 |
+
device (Optional str): device used for predicting, either "cuda" or "cpu", None defaults to cuda if available.
|
| 157 |
+
ckpt_forward (Optional str): name of the forward model without extension. Should exist inside the "models/forward" directory and be a .ckpt file.
|
| 158 |
+
ckpt_retro (Optional str): name of the retrosynthesis model without extension. Should exist inside the "models/retrosynthesis" directory and be a .ckpt file.
|
| 159 |
+
vocab (Optional str): name of the vocab file without extension. Should exist inside the "vocab" directory and be a .txt file.
|
| 160 |
+
"""
|
| 161 |
+
try:
|
| 162 |
+
kwargs = {
|
| 163 |
+
"topn": topn,
|
| 164 |
+
"num_beams": num_beams,
|
| 165 |
+
"fap": fap,
|
| 166 |
+
"fld": fld,
|
| 167 |
+
"max_depth": max_depth,
|
| 168 |
+
"beam_width": beam_width,
|
| 169 |
+
"device": device,
|
| 170 |
+
"ckpt_forward": ckpt_forward,
|
| 171 |
+
"ckpt_retro": ckpt_retro,
|
| 172 |
+
"vocab": vocab,
|
| 173 |
+
}
|
| 174 |
+
task = app.send_task(
|
| 175 |
+
"tasks.retro_prediction_tree",
|
| 176 |
+
[product],
|
| 177 |
+
kwargs=kwargs,
|
| 178 |
+
queue="retro_prediction",
|
| 179 |
+
)
|
| 180 |
+
result = await wait_for_celery_result(task, 1500)
|
| 181 |
+
result["status"] = "success"
|
| 182 |
+
return result
|
| 183 |
+
except TimeoutError:
|
| 184 |
+
return {
|
| 185 |
+
"status": "timeout",
|
| 186 |
+
"message": "Computation exceeded time limit",
|
| 187 |
+
}
|
| 188 |
+
except Exception as e:
|
| 189 |
+
return {
|
| 190 |
+
"status": "error",
|
| 191 |
+
"message": str(e),
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
if __name__ == "__main__":
|
| 196 |
+
mcp.settings.host = "0.0.0.0"
|
| 197 |
+
mcp.settings.port = 8001
|
| 198 |
+
asyncio.run(mcp.run_streamable_http_async())
|
mcp/start.sh
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/sh
|
| 2 |
+
set -e
|
| 3 |
+
|
| 4 |
+
echo "Starting FastMCP on port 8001"
|
| 5 |
+
python server.py &
|
| 6 |
+
|
| 7 |
+
echo "Starting MCPO on port 8000"
|
| 8 |
+
exec mcpo \
|
| 9 |
+
--host 0.0.0.0 \
|
| 10 |
+
--port 8000 \
|
| 11 |
+
--server-type streamable_http \
|
| 12 |
+
-- http://127.0.0.1:8001/mcp
|
model-training/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Model Training
|
| 2 |
+
|
| 3 |
+
This directory contains everything needed to retrain the forward prediction and retrosynthesis models.
|
| 4 |
+
|
| 5 |
+
- **`pistachio/`** — Scripts to download and preprocess Pistachio reaction data into the JSONL format required for training. See [`pistachio/README.md`](pistachio/README.md) for the full data pipeline.
|
| 6 |
+
- **`training/`** — The LightningCLI entry point (`cli_main.py`) and YAML configuration files for training, testing, and running predictions.
|
| 7 |
+
- **`tools/`** — Standalone utility scripts for data analysis, model diagnostics, and running training/testing/prediction directly without LightningCLI.
|
| 8 |
+
|
| 9 |
+
The model source code lives in [`worker/transformers_model/`](../worker/transformers_model/). The key files are:
|
| 10 |
+
- [`model.py`](../worker/transformers_model/model.py) — `LitVanillaTransformer`, the Lightning module that wraps the model.
|
| 11 |
+
- [`smiles_datamodule.py`](../worker/transformers_model/smiles_datamodule.py) — `LitSmilesDataset`, the Lightning data module.
|
| 12 |
+
- [`configuration.py`](../worker/transformers_model/configuration.py) — `VanillaTransformerConfig`, the model's default hyperparameter configuration.
|
| 13 |
+
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
## Setup
|
| 17 |
+
|
| 18 |
+
### Copy the model package
|
| 19 |
+
|
| 20 |
+
`cli_main.py` imports `transformers_model` as a local package. Python has no way to resolve this import across the repository boundary to `worker/transformers_model/` without installing it, so the folder must be copied into `model-training/` before running any command:
|
| 21 |
+
|
| 22 |
+
```bash
|
| 23 |
+
cp -r ../worker/transformers_model model-training/transformers_model
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
This only needs to be done once, or again whenever the model source in `worker/` changes.
|
| 27 |
+
|
| 28 |
+
### Install dependencies
|
| 29 |
+
|
| 30 |
+
#### With uv (recommended)
|
| 31 |
+
|
| 32 |
+
```bash
|
| 33 |
+
# Install uv if not already available
|
| 34 |
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
| 35 |
+
|
| 36 |
+
# Create and activate a virtual environment
|
| 37 |
+
uv venv --python 3.11
|
| 38 |
+
source .venv/bin/activate
|
| 39 |
+
|
| 40 |
+
# Install dependencies
|
| 41 |
+
uv pip install -r requirements.txt
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
#### Without uv
|
| 45 |
+
|
| 46 |
+
```bash
|
| 47 |
+
python3.11 -m venv .venv
|
| 48 |
+
source .venv/bin/activate
|
| 49 |
+
pip install -r requirements.txt
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
> On Windows replace `source .venv/bin/activate` with `.venv\Scripts\activate`.
|
| 53 |
+
|
| 54 |
+
---
|
| 55 |
+
|
| 56 |
+
## Running LightningCLI commands
|
| 57 |
+
|
| 58 |
+
All commands are run from the `model-training/` directory using `cli_main.py` together with one of the provided YAML configuration files.
|
| 59 |
+
|
| 60 |
+
### Training
|
| 61 |
+
|
| 62 |
+
```bash
|
| 63 |
+
# Forward model — with uv
|
| 64 |
+
uv run python training/cli_main.py fit --config=training/train.yaml
|
| 65 |
+
|
| 66 |
+
# Forward model — without uv (venv already activated)
|
| 67 |
+
python training/cli_main.py fit --config=training/train.yaml
|
| 68 |
+
|
| 69 |
+
# Retrosynthesis model
|
| 70 |
+
python training/cli_main.py fit --config=training/train_retro.yaml
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
### Testing
|
| 74 |
+
|
| 75 |
+
```bash
|
| 76 |
+
# Forward model
|
| 77 |
+
python training/cli_main.py test --config=training/test.yaml
|
| 78 |
+
|
| 79 |
+
# Retrosynthesis model
|
| 80 |
+
python training/cli_main.py test --config=training/test_retro.yaml
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
### Prediction
|
| 84 |
+
|
| 85 |
+
```bash
|
| 86 |
+
python training/cli_main.py predict --config=training/predict.yaml
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
---
|
| 90 |
+
|
| 91 |
+
## Configuration
|
| 92 |
+
|
| 93 |
+
Default values are defined in [`VanillaTransformerConfig`](../worker/transformers_model/configuration.py). Additional defaults are set directly in [`LitVanillaTransformer`](../worker/transformers_model/model.py) and [`LitSmilesDataset`](../worker/transformers_model/smiles_datamodule.py). Note that some defaults from `VanillaTransformerConfig` are overridden inside `LitVanillaTransformer`, so always verify which value is actually used at runtime.
|
| 94 |
+
|
| 95 |
+
Lightning saves the configuration used for each run to `hparams.yaml` inside the corresponding `lightning_logs/<version>/` folder.
|
| 96 |
+
|
| 97 |
+
### Passing options inline
|
| 98 |
+
|
| 99 |
+
Any option from a YAML file can also be passed directly on the command line:
|
| 100 |
+
|
| 101 |
+
**Model flags** — forwarded to `LitVanillaTransformer.__init__`:
|
| 102 |
+
```
|
| 103 |
+
--model.vocab_path=vocab/vocab.txt
|
| 104 |
+
--model.learning_rate=0.0002
|
| 105 |
+
--model.task=forward
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
**Data flags** — forwarded to `LitSmilesDataset.__init__`:
|
| 109 |
+
```
|
| 110 |
+
--data.vocab_path=vocab/vocab.txt
|
| 111 |
+
--data.train_path=data/pistachio/data.forward/train.jsonl
|
| 112 |
+
--data.validation_path=data/pistachio/data.forward/validation.jsonl
|
| 113 |
+
--data.num_dataloader_workers=16
|
| 114 |
+
--data.batch_size=256
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
**Trainer flags** — forwarded to the Lightning `Trainer`:
|
| 118 |
+
```
|
| 119 |
+
--trainer.max_epochs=10
|
| 120 |
+
--trainer.accelerator=gpu
|
| 121 |
+
--trainer.devices=1
|
| 122 |
+
--trainer.precision=32
|
| 123 |
+
--trainer.enable_progress_bar=false
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
**Global flags**:
|
| 127 |
+
```
|
| 128 |
+
--seed_everything=42
|
| 129 |
+
--config=training/train.yaml
|
| 130 |
+
```
|
| 131 |
+
|
| 132 |
+
### Example YAML with callbacks
|
| 133 |
+
|
| 134 |
+
More complex options, such as callbacks, are easier to specify via a YAML file:
|
| 135 |
+
|
| 136 |
+
```yaml
|
| 137 |
+
model:
|
| 138 |
+
vocab_path: vocab/vocab.txt
|
| 139 |
+
learning_rate: 0.0002
|
| 140 |
+
data:
|
| 141 |
+
vocab_path: vocab/vocab.txt
|
| 142 |
+
train_path: data/pistachio/data.forward/train.jsonl
|
| 143 |
+
validation_path: data/pistachio/data.forward/validation.jsonl
|
| 144 |
+
num_dataloader_workers: 16
|
| 145 |
+
batch_size: 256
|
| 146 |
+
trainer:
|
| 147 |
+
max_epochs: 10
|
| 148 |
+
accelerator: gpu
|
| 149 |
+
enable_progress_bar: false
|
| 150 |
+
devices: 1
|
| 151 |
+
precision: 32
|
| 152 |
+
callbacks:
|
| 153 |
+
- class_path: lightning.pytorch.callbacks.ModelCheckpoint
|
| 154 |
+
init_args:
|
| 155 |
+
monitor: val_accuracy
|
| 156 |
+
mode: max
|
| 157 |
+
save_top_k: 3
|
| 158 |
+
filename: "{epoch:02d}-{val_accuracy:.4f}"
|
| 159 |
+
```
|
| 160 |
+
|
| 161 |
+
---
|
| 162 |
+
|
| 163 |
+
## Requirements
|
| 164 |
+
|
| 165 |
+
`requirements.txt` covers all dependencies for both model training and the Pistachio data pipeline:
|
| 166 |
+
|
| 167 |
+
| Package | Used by |
|
| 168 |
+
|---------|---------|
|
| 169 |
+
| `lightning` | LightningCLI, model and data modules |
|
| 170 |
+
| `pandas` | Pistachio data pipeline (steps 7 and 8) |
|
| 171 |
+
| `python-Levenshtein` | Edit-distance metrics during validation and testing |
|
| 172 |
+
| `rdkit` | SMILES validation and Tanimoto similarity during training |
|
| 173 |
+
| `rxn-chem-utils` | SMILES tokenization regex pattern |
|
| 174 |
+
| `torch` | Model training |
|
| 175 |
+
| `transformers` | Tokenizer, scheduler, model base classes |
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
---
|
| 179 |
+
|
| 180 |
+
## Improving training throughput
|
| 181 |
+
|
| 182 |
+
- **`num_dataloader_workers`** — Number of CPU workers for data loading. 16 has shown the best results in practice.
|
| 183 |
+
- **`batch_size`** — Samples per training step. Increasing from the default 32 to 256 (or higher) significantly reduces wall time per epoch.
|
| 184 |
+
- **`accumulate_grad_batches`** — Simulates a larger effective batch size without increasing GPU memory usage.
|
| 185 |
+
- **`precision`** — `32` is the stable default. `16-mixed` can speed up training but has shown instability (NaN loss values) in practice.
|
| 186 |
+
- **`profiler`** — Set `trainer.profiler: simple` to identify time bottlenecks across training steps.
|
model-training/pistachio/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Pistachio Data Pipeline
|
| 2 |
+
|
| 3 |
+
This directory contains a sequence of scripts that download and process [Pistachio](https://www.nextmovesoftware.com/pistachio.html) reaction data into the JSONL format expected by the forward and retrosynthesis model training pipelines.
|
| 4 |
+
|
| 5 |
+
## Prerequisites
|
| 6 |
+
|
| 7 |
+
- **Pistachio license** — Access to the Pistachio release archives requires a license from [NextMove Software](https://www.nextmovesoftware.com/). Obtain your credentials before running step 1.
|
| 8 |
+
- **`rxn-reaction-preprocessing`** — Provides the `rxn-data-pipeline` CLI used in steps 4 and 6. Install it from [rxn4chemistry/rxn-reaction-preprocessing](https://github.com/rxn4chemistry/rxn-reaction-preprocessing/).
|
| 9 |
+
- **Python dependencies** — `pandas` is required for steps 7 and 8.
|
| 10 |
+
|
| 11 |
+
> **Working directory**: unless noted otherwise, all scripts assume they are run from the root of the repository, and data is written under `data/pistachio/`.
|
| 12 |
+
|
| 13 |
+
---
|
| 14 |
+
|
| 15 |
+
## Step 1 — Download the Pistachio archive
|
| 16 |
+
|
| 17 |
+
Edit `step1_download.sh` to set the desired release version and replace `<USER>` and `<PASSWORD>` with your NextMove Software credentials, then run:
|
| 18 |
+
|
| 19 |
+
```bash
|
| 20 |
+
bash model-training/pistachio/step1_download.sh
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
This downloads `pistachio.tar.gz` for the configured release into the current directory. The `-C -` flag enables resuming an interrupted download.
|
| 24 |
+
|
| 25 |
+
---
|
| 26 |
+
|
| 27 |
+
## Step 2 — Extract reaction SMILES
|
| 28 |
+
|
| 29 |
+
```bash
|
| 30 |
+
python model-training/pistachio/step2_get_reactions.py
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
Iterates over every record in `pistachio.tar.gz` and writes one reaction SMILES per line to `reactions.txt`.
|
| 34 |
+
|
| 35 |
+
The input and output paths can be overridden if needed:
|
| 36 |
+
|
| 37 |
+
```bash
|
| 38 |
+
python model-training/pistachio/step2_get_reactions.py \
|
| 39 |
+
--input path/to/pistachio.tar.gz \
|
| 40 |
+
--output path/to/reactions.txt
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
Both a `.tar.gz` archive and an already-extracted directory of JSON files are accepted as input.
|
| 44 |
+
|
| 45 |
+
---
|
| 46 |
+
|
| 47 |
+
## Step 3 — Remove extended SMILES annotations
|
| 48 |
+
|
| 49 |
+
```bash
|
| 50 |
+
python model-training/pistachio/step3_clean_reactions.py
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
Some reaction SMILES contain extended SMILES notation blocks such as `|&1:4,24,f:2.4|` or `|f:1.2|` that are not handled correctly by downstream tools. This script strips everything after the first `|` character on each line and writes the result to `reactions_cleaned.txt`.
|
| 54 |
+
|
| 55 |
+
---
|
| 56 |
+
|
| 57 |
+
## Step 4 — Standardize reactions (IMPORT + STANDARDIZE)
|
| 58 |
+
|
| 59 |
+
```bash
|
| 60 |
+
rxn-data-pipeline \
|
| 61 |
+
--config-dir model-training/pistachio \
|
| 62 |
+
--config-name step4_standardize_reactions_config.yaml
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
Runs the **IMPORT** and **STANDARDIZE** stages of the `rxn-data-pipeline` on `reactions_cleaned.txt`. After completion, the pipeline directory (`data/pistachio/pipeline/`) will contain:
|
| 66 |
+
|
| 67 |
+
| File | Description |
|
| 68 |
+
|------|-------------|
|
| 69 |
+
| `data.imported.csv` | Raw reactions imported into the pipeline CSV format |
|
| 70 |
+
| `data.standardized.csv` | Reactions after SMILES standardization |
|
| 71 |
+
|
| 72 |
+
> The full pipeline will fail at this point because a subset of standardized reactions contain coordinate-bond arrow annotations such as `->[Cu+2]<-`. Step 5 removes these before the pipeline is resumed.
|
| 73 |
+
|
| 74 |
+
---
|
| 75 |
+
|
| 76 |
+
## Step 5 — Remove arrow annotations
|
| 77 |
+
|
| 78 |
+
```bash
|
| 79 |
+
bash model-training/pistachio/step5_remove_arrow_annotations.sh
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
Filters `data.standardized.csv` to remove any rows whose reaction SMILES contain arrow-bond annotations (`->` or `<-`). The removed rows are saved to `arrow.forward.csv` and `arrow.backward.csv` for inspection. The cleaned output is written to `arrow.removed.csv`, which is the input for the next step.
|
| 83 |
+
|
| 84 |
+
---
|
| 85 |
+
|
| 86 |
+
## Step 6 — Preprocess and split reactions (PREPROCESS + SPLIT)
|
| 87 |
+
|
| 88 |
+
```bash
|
| 89 |
+
rxn-data-pipeline \
|
| 90 |
+
--config-dir model-training/pistachio \
|
| 91 |
+
--config-name step6_preprocess_reactions_config.yaml
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
Resumes the pipeline from `arrow.removed.csv`, running the **PREPROCESS** and **SPLIT** stages. After completion, the pipeline directory will contain:
|
| 95 |
+
|
| 96 |
+
| File | Description |
|
| 97 |
+
|------|-------------|
|
| 98 |
+
| `data.processed.train.csv` | Training split |
|
| 99 |
+
| `data.processed.test.csv` | Test split |
|
| 100 |
+
| `data.processed.validation.csv` | Validation split |
|
| 101 |
+
|
| 102 |
+
The split ratio is set to 5 % for test and validation (see `step6_preprocess_reactions_config.yaml`).
|
| 103 |
+
|
| 104 |
+
---
|
| 105 |
+
|
| 106 |
+
## Step 7 — Convert splits to JSONL
|
| 107 |
+
|
| 108 |
+
```bash
|
| 109 |
+
python model-training/pistachio/step7_convert_to_jsonl.py
|
| 110 |
+
```
|
| 111 |
+
|
| 112 |
+
Reads the three CSV splits and writes them as JSONL files in the format expected by the model trainer (`{"source": "...", "target": "..."}`). Two sets of output files are produced — one for each task direction:
|
| 113 |
+
|
| 114 |
+
| Directory | Task | source | target |
|
| 115 |
+
|-----------|------|--------|--------|
|
| 116 |
+
| `data/pistachio/data.forward/` | Forward prediction | reactants | products |
|
| 117 |
+
| `data/pistachio/data.retro/` | Retrosynthesis | products | reactants |
|
| 118 |
+
|
| 119 |
+
Each directory contains `train.jsonl`, `test.jsonl`, and `validation.jsonl`.
|
| 120 |
+
|
| 121 |
+
---
|
| 122 |
+
|
| 123 |
+
## Step 8 — Generate model vocabulary
|
| 124 |
+
|
| 125 |
+
The vocabulary can be built with either the forward or retro data, from its training and validation splits, since the data is the same with only the source and target being switched.
|
| 126 |
+
|
| 127 |
+
```bash
|
| 128 |
+
python model-training/pistachio/step8_generate_vocab.py \
|
| 129 |
+
--data-dir data/pistachio/data.forward \
|
| 130 |
+
--output data/pistachio/vocab.txt
|
| 131 |
+
```
|
| 132 |
+
|
| 133 |
+
The script tokenizes all SMILES strings in `train.jsonl` and `validation.jsonl`, counts every token (bracketed atoms, two-character elements such as `Cl`/`Br`, `%nn` ring-closure labels, and individual characters), and writes a `vocab.txt` file with reserved BERT tokens prepended and all remaining tokens ordered by frequency.
|
model-training/pistachio/step1_download.sh
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
BASE_URL=https://www.nextmovesoftware.com/downloads/pistachio/releases/
|
| 4 |
+
RELEASE=2025Q2
|
| 5 |
+
curl -L -C - --user <USER>:<PASSWORD> $BASE_URL/$RELEASE/data/pistachio.tar.gz -o pistachio.tar.gz
|
model-training/pistachio/step2_get_reactions.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Extract reaction SMILES from a Pistachio archive.
|
| 3 |
+
|
| 4 |
+
This script is self-contained: all logic that was previously provided by the
|
| 5 |
+
rxn_pistachio package has been inlined here. No external dependencies beyond
|
| 6 |
+
the Python standard library are required.
|
| 7 |
+
|
| 8 |
+
Usage:
|
| 9 |
+
python step2_get_reactions.py # defaults below
|
| 10 |
+
python step2_get_reactions.py --input my.tar.gz --output reactions.txt
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import argparse
|
| 14 |
+
import json
|
| 15 |
+
import logging
|
| 16 |
+
import tarfile
|
| 17 |
+
from pathlib import Path
|
| 18 |
+
from typing import Any, Dict, Iterator, Union
|
| 19 |
+
|
| 20 |
+
# ---------------------------------------------------------------------------
|
| 21 |
+
# Pistachio archive navigation (inlined from rxn_pistachio)
|
| 22 |
+
# ---------------------------------------------------------------------------
|
| 23 |
+
|
| 24 |
+
def _is_json(path: Union[str, Path]) -> bool:
|
| 25 |
+
path_str = str(path)
|
| 26 |
+
return path_str.endswith(".json") or path_str.endswith(".JSON")
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def _iter_reaction_dicts(archive_path: Path) -> Iterator[Dict[str, Any]]:
|
| 30 |
+
"""
|
| 31 |
+
Iterate over every reaction JSON object inside a Pistachio .tar.gz file
|
| 32 |
+
or a directory tree of JSON files.
|
| 33 |
+
"""
|
| 34 |
+
if archive_path.is_dir():
|
| 35 |
+
for json_file in (p for p in archive_path.rglob("*") if _is_json(p)):
|
| 36 |
+
print(f'Reading file "{json_file}"')
|
| 37 |
+
with open(json_file, "rt") as f:
|
| 38 |
+
for line in f:
|
| 39 |
+
line = line.strip()
|
| 40 |
+
if line:
|
| 41 |
+
yield json.loads(line)
|
| 42 |
+
else:
|
| 43 |
+
with tarfile.open(archive_path, "r:gz") as tar:
|
| 44 |
+
for member in tar.getmembers():
|
| 45 |
+
if not _is_json(member.name):
|
| 46 |
+
continue
|
| 47 |
+
extracted = tar.extractfile(member)
|
| 48 |
+
if extracted is None:
|
| 49 |
+
continue
|
| 50 |
+
for raw_line in extracted:
|
| 51 |
+
line = raw_line.strip()
|
| 52 |
+
if line:
|
| 53 |
+
yield json.loads(line)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def _reaction_smiles(record: Dict[str, Any]) -> str:
|
| 57 |
+
"""
|
| 58 |
+
Return the reaction SMILES string from a Pistachio record dictionary.
|
| 59 |
+
Mirrors Record.reaction_smiles from rxn_pistachio.
|
| 60 |
+
"""
|
| 61 |
+
data = record.get("data", {})
|
| 62 |
+
if "reactionSmiles" in data:
|
| 63 |
+
return data["reactionSmiles"]
|
| 64 |
+
return data.get("smiles", "")
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
# ---------------------------------------------------------------------------
|
| 68 |
+
# Main
|
| 69 |
+
# ---------------------------------------------------------------------------
|
| 70 |
+
|
| 71 |
+
def extract_reactions(input_path: str, output_path: str) -> None:
|
| 72 |
+
archive = Path(input_path)
|
| 73 |
+
if not archive.exists():
|
| 74 |
+
raise FileNotFoundError(f"Input not found: {archive}")
|
| 75 |
+
|
| 76 |
+
written = 0
|
| 77 |
+
skipped = 0
|
| 78 |
+
|
| 79 |
+
with open(output_path, "w") as out:
|
| 80 |
+
for record in _iter_reaction_dicts(archive):
|
| 81 |
+
smiles = _reaction_smiles(record)
|
| 82 |
+
if smiles:
|
| 83 |
+
out.write(smiles + "\n")
|
| 84 |
+
written += 1
|
| 85 |
+
else:
|
| 86 |
+
skipped += 1
|
| 87 |
+
|
| 88 |
+
print(f"Done. Written: {written}, skipped (no SMILES): {skipped}")
|
| 89 |
+
print(f"Output: {output_path}")
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
if __name__ == "__main__":
|
| 93 |
+
parser = argparse.ArgumentParser(
|
| 94 |
+
description="Extract reaction SMILES from a Pistachio archive."
|
| 95 |
+
)
|
| 96 |
+
parser.add_argument(
|
| 97 |
+
"--input",
|
| 98 |
+
default="pistachio.tar.gz",
|
| 99 |
+
help="Path to pistachio.tar.gz or extracted directory (default: pistachio.tar.gz)",
|
| 100 |
+
)
|
| 101 |
+
parser.add_argument(
|
| 102 |
+
"--output",
|
| 103 |
+
default="reactions.txt",
|
| 104 |
+
help="Output file path (default: reactions.txt)",
|
| 105 |
+
)
|
| 106 |
+
args = parser.parse_args()
|
| 107 |
+
extract_reactions(args.input, args.output)
|
model-training/pistachio/step3_clean_reactions.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
input_file = 'reactions.txt'
|
| 3 |
+
output_file = 'reactions_cleaned.txt'
|
| 4 |
+
|
| 5 |
+
with open(input_file, 'r') as infile, open(output_file, 'w') as outfile:
|
| 6 |
+
for line in infile:
|
| 7 |
+
cleaned_line = line.split('|')[0].strip()
|
| 8 |
+
outfile.write(cleaned_line + '\n')
|
| 9 |
+
|
| 10 |
+
print(f"Cleaned SMILES reactions saved to '{output_file}'")
|
model-training/pistachio/step4_standardize_reactions_config.yaml
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
data:
|
| 2 |
+
path: data/pistachio/reactions_cleaned.txt
|
| 3 |
+
name: data
|
| 4 |
+
proc_dir: data/pistachio/pipeline
|
| 5 |
+
common:
|
| 6 |
+
sequence:
|
| 7 |
+
- IMPORT
|
| 8 |
+
- STANDARDIZE
|
| 9 |
+
fragment_bond: TILDE
|
| 10 |
+
reaction_column_name: rxn
|
| 11 |
+
keep_intermediate_columns: false
|
| 12 |
+
rxn_import:
|
| 13 |
+
input_file: data/pistachio/reactions_cleaned.txt
|
| 14 |
+
output_csv: data/pistachio/pipeline/data.imported.csv
|
| 15 |
+
data_format: TXT
|
| 16 |
+
input_csv_column_name: rxn
|
| 17 |
+
reaction_column_name: rxn
|
| 18 |
+
fragment_bond: TILDE
|
| 19 |
+
remove_atom_mapping: true
|
| 20 |
+
column_for_light: null
|
| 21 |
+
column_for_heat: null
|
| 22 |
+
keep_original_rxn_column: false
|
| 23 |
+
standardize:
|
| 24 |
+
input_file_path: data/pistachio/pipeline/data.imported.csv
|
| 25 |
+
annotation_file_paths: []
|
| 26 |
+
discard_unannotated_metals: false
|
| 27 |
+
output_file_path: data/pistachio/pipeline/data.standardized.csv
|
| 28 |
+
fragment_bond: TILDE
|
| 29 |
+
reaction_column_name: rxn
|
| 30 |
+
remove_stereo_if_not_defined_in_precursors: false
|
| 31 |
+
keep_intermediate_columns: false
|
model-training/pistachio/step5_remove_arrow_annotations.sh
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
BASE_PATH=data/pistachio/pipeline
|
| 4 |
+
FILEPATH=$BASE_PATH/data.standardized.csv
|
| 5 |
+
|
| 6 |
+
# Extract the header
|
| 7 |
+
head -n 1 $FILEPATH > $BASE_PATH/arrow.backward.csv
|
| 8 |
+
head -n 1 $FILEPATH > $BASE_PATH/arrow.forward.csv
|
| 9 |
+
head -n 1 $FILEPATH > $BASE_PATH/arrow.removed.csv
|
| 10 |
+
head -n 1 $FILEPATH > $BASE_PATH/tmp.csv
|
| 11 |
+
|
| 12 |
+
# Append matching lines (excluding header) to each file
|
| 13 |
+
tail -n +2 $FILEPATH | grep '<' >> $BASE_PATH/arrow.backward.csv
|
| 14 |
+
tail -n +2 $FILEPATH | grep -v '<' >> $BASE_PATH/tmp.csv
|
| 15 |
+
tail -n +2 $BASE_PATH/tmp.csv | grep "\\->" >> $BASE_PATH/arrow.forward.csv
|
| 16 |
+
tail -n +2 $BASE_PATH/tmp.csv | grep -v "\\->" >> $BASE_PATH/arrow.removed.csv
|
| 17 |
+
|
| 18 |
+
# Remove auxiliary csv
|
| 19 |
+
rm $BASE_PATH/tmp.csv
|
model-training/pistachio/step6_preprocess_reactions_config.yaml
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
data:
|
| 2 |
+
path: data/pistachio/reactions_cleaned.txt
|
| 3 |
+
name: data
|
| 4 |
+
proc_dir: data/pistachio/pipeline
|
| 5 |
+
common:
|
| 6 |
+
sequence:
|
| 7 |
+
- PREPROCESS
|
| 8 |
+
- SPLIT
|
| 9 |
+
fragment_bond: TILDE
|
| 10 |
+
reaction_column_name: rxn
|
| 11 |
+
keep_intermediate_columns: false
|
| 12 |
+
preprocess:
|
| 13 |
+
input_file_path: data/pistachio/pipeline/arrow.removed.csv
|
| 14 |
+
output_file_path: data/pistachio/pipeline/data.processed.csv
|
| 15 |
+
min_reactants: 1
|
| 16 |
+
max_reactants: 10
|
| 17 |
+
max_reactants_tokens: 254
|
| 18 |
+
min_agents: 0
|
| 19 |
+
max_agents: 0
|
| 20 |
+
max_agents_tokens: 0
|
| 21 |
+
min_products: 1
|
| 22 |
+
max_products: 10
|
| 23 |
+
max_products_tokens: 254
|
| 24 |
+
max_absolute_formal_charge: 2
|
| 25 |
+
fragment_bond: TILDE
|
| 26 |
+
reaction_column_name: rxn
|
| 27 |
+
keep_intermediate_columns: false
|
| 28 |
+
split:
|
| 29 |
+
input_file_path: data/pistachio/pipeline/data.processed.csv
|
| 30 |
+
output_directory: data/pistachio/pipeline
|
| 31 |
+
split_ratio: 0.05
|
| 32 |
+
reaction_column_name: rxn
|
| 33 |
+
index_column: products
|
| 34 |
+
hash_seed: 42
|
| 35 |
+
shuffle_seed: 42
|
model-training/pistachio/step7_convert_to_jsonl.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pandas as pd
|
| 2 |
+
import os
|
| 3 |
+
import json
|
| 4 |
+
|
| 5 |
+
# Define base paths
|
| 6 |
+
base_path = "data/pistachio"
|
| 7 |
+
data_path = os.path.join(base_path, "pipeline")
|
| 8 |
+
|
| 9 |
+
# Define input CSV files
|
| 10 |
+
csv_files = {
|
| 11 |
+
"train": os.path.join(data_path, "data.processed.train.csv"),
|
| 12 |
+
"test": os.path.join(data_path, "data.processed.test.csv"),
|
| 13 |
+
"validation": os.path.join(data_path, "data.processed.validation.csv")
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
# Define output directories
|
| 17 |
+
forward_dir = os.path.join(base_path, "data.forward")
|
| 18 |
+
retro_dir = os.path.join(base_path, "data.retro")
|
| 19 |
+
os.makedirs(forward_dir, exist_ok=True)
|
| 20 |
+
os.makedirs(retro_dir, exist_ok=True)
|
| 21 |
+
|
| 22 |
+
# Process each dataset
|
| 23 |
+
for split, csv_file in csv_files.items():
|
| 24 |
+
# Read CSV file
|
| 25 |
+
df = pd.read_csv(csv_file, header=0, names=["reactants", "products"], sep=">>", engine="python")
|
| 26 |
+
|
| 27 |
+
# Escape backslashes
|
| 28 |
+
df["reactants"] = df["reactants"].astype(str)
|
| 29 |
+
df["products"] = df["products"].astype(str)
|
| 30 |
+
|
| 31 |
+
# Write forward JSONL
|
| 32 |
+
forward_file = os.path.join(forward_dir, f"{split}.jsonl")
|
| 33 |
+
with open(forward_file, "w") as f_out:
|
| 34 |
+
for _, row in df.iterrows():
|
| 35 |
+
json.dump({"source": row["reactants"], "target": row["products"]}, f_out)
|
| 36 |
+
f_out.write("\n")
|
| 37 |
+
|
| 38 |
+
# Write retro JSONL
|
| 39 |
+
retro_file = os.path.join(retro_dir, f"{split}.jsonl")
|
| 40 |
+
with open(retro_file, "w") as f_out:
|
| 41 |
+
for _, row in df.iterrows():
|
| 42 |
+
json.dump({"source": row["products"], "target": row["reactants"]}, f_out)
|
| 43 |
+
f_out.write("\n")
|
| 44 |
+
|
| 45 |
+
print("All JSONL files created successfully.")
|
| 46 |
+
|
model-training/pistachio/step8_generate_vocab.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
import re
|
| 4 |
+
from collections import Counter
|
| 5 |
+
|
| 6 |
+
# Reserved tokens
|
| 7 |
+
reserved_tokens = [
|
| 8 |
+
"[PAD]", "[unused1]", "[unused2]", "[unused3]", "[unused4]", "[unused5]",
|
| 9 |
+
"[unused6]", "[unused7]", "[unused8]", "[unused9]", "[unused10]", "[UNK]",
|
| 10 |
+
"[CLS]", "[SEP]", "[MASK]"
|
| 11 |
+
]
|
| 12 |
+
|
| 13 |
+
def count_characters(file_path):
|
| 14 |
+
char_counter = Counter()
|
| 15 |
+
with open(file_path, 'r') as file:
|
| 16 |
+
for line in file:
|
| 17 |
+
data = json.loads(line)
|
| 18 |
+
source = data.get('source', '')
|
| 19 |
+
target = data.get('target', '')
|
| 20 |
+
combined = source + target
|
| 21 |
+
|
| 22 |
+
# Extract and count bracketed tokens
|
| 23 |
+
bracket_tokens = re.findall(r'\[.*?\]', combined)
|
| 24 |
+
char_counter.update(bracket_tokens)
|
| 25 |
+
|
| 26 |
+
# Remove bracketed tokens
|
| 27 |
+
combined = re.sub(r'\[.*?\]', '', combined)
|
| 28 |
+
|
| 29 |
+
# Extract and count composite % tokens (e.g., %10, %11)
|
| 30 |
+
percent_tokens = re.findall(r'%\d{2}', combined)
|
| 31 |
+
char_counter.update(percent_tokens)
|
| 32 |
+
|
| 33 |
+
# Remove composite % tokens
|
| 34 |
+
combined = re.sub(r'%\d{2}', '', combined)
|
| 35 |
+
|
| 36 |
+
# Tokenize remaining string
|
| 37 |
+
tokens = []
|
| 38 |
+
i = 0
|
| 39 |
+
while i < len(combined):
|
| 40 |
+
if combined[i:i+2] in ('Cl', 'Br'):
|
| 41 |
+
tokens.append(combined[i:i+2])
|
| 42 |
+
i += 2
|
| 43 |
+
else:
|
| 44 |
+
tokens.append(combined[i])
|
| 45 |
+
i += 1
|
| 46 |
+
|
| 47 |
+
char_counter.update(tokens)
|
| 48 |
+
|
| 49 |
+
return char_counter
|
| 50 |
+
|
| 51 |
+
def generate_vocab(train_file, val_file, output_file):
|
| 52 |
+
train_counter = count_characters(train_file)
|
| 53 |
+
val_counter = count_characters(val_file)
|
| 54 |
+
total_counter = train_counter + val_counter
|
| 55 |
+
sorted_tokens = [token for token, _ in total_counter.most_common()]
|
| 56 |
+
vocab = reserved_tokens + sorted_tokens
|
| 57 |
+
with open(output_file, 'w') as file:
|
| 58 |
+
for token in vocab:
|
| 59 |
+
file.write(token + '\n')
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
if __name__ == "__main__":
|
| 63 |
+
import argparse
|
| 64 |
+
|
| 65 |
+
parser = argparse.ArgumentParser(
|
| 66 |
+
description="Generate a vocabulary file from forward or retro JSONL splits."
|
| 67 |
+
)
|
| 68 |
+
parser.add_argument(
|
| 69 |
+
"--data-dir",
|
| 70 |
+
required=True,
|
| 71 |
+
help="Directory containing train.jsonl and validation.jsonl (e.g. data/pistachio/data.forward.256)",
|
| 72 |
+
)
|
| 73 |
+
parser.add_argument(
|
| 74 |
+
"--output",
|
| 75 |
+
default="vocab.txt",
|
| 76 |
+
help="Output vocabulary file path (default: vocab.txt)",
|
| 77 |
+
)
|
| 78 |
+
args = parser.parse_args()
|
| 79 |
+
|
| 80 |
+
train_file = os.path.join(args.data_dir, "train.jsonl")
|
| 81 |
+
val_file = os.path.join(args.data_dir, "validation.jsonl")
|
| 82 |
+
generate_vocab(train_file, val_file, args.output)
|
| 83 |
+
print(f"Vocabulary written to '{args.output}'")
|
model-training/requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
lightning
|
| 2 |
+
pandas
|
| 3 |
+
python-Levenshtein
|
| 4 |
+
rdkit
|
| 5 |
+
rxn-chem-utils
|
| 6 |
+
torch
|
| 7 |
+
transformers
|
model-training/tools/analyze_explicit_hydrogens.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Script to analyze reactions with explicit hydrogens and check for organometallic elements.
|
| 4 |
+
Usage: python check_explicit_hydrogens.py <input_file>
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import sys
|
| 8 |
+
import re
|
| 9 |
+
from collections import Counter
|
| 10 |
+
|
| 11 |
+
# Common organometallic and metalloid elements
|
| 12 |
+
METALS = {
|
| 13 |
+
'Li', 'Na', 'K', 'Mg', 'Ca', 'Sc', 'Ti', 'V', 'Cr', 'Mn', 'Fe', 'Co', 'Ni',
|
| 14 |
+
'Cu', 'Zn', 'Al', 'Ga', 'Ge', 'Sn', 'Pb', 'B', 'Si', 'P', 'Pd', 'Pt', 'Rh',
|
| 15 |
+
'Ru', 'Os', 'Ir', 'Au', 'Ag', 'Cd', 'Hg', 'Zr', 'Mo', 'W', 'Re', 'Bi'
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
def has_explicit_hydrogens(smiles):
|
| 19 |
+
"""Check if SMILES contains explicit hydrogens like [CH], [CH2], [CH3]"""
|
| 20 |
+
return bool(re.search(r'\[CH\d?\]', smiles))
|
| 21 |
+
|
| 22 |
+
def extract_elements(smiles):
|
| 23 |
+
"""Extract all elements in square brackets from SMILES"""
|
| 24 |
+
# Match elements in square brackets, including charges and other notation
|
| 25 |
+
pattern = r'\[([A-Z][a-z]?)'
|
| 26 |
+
elements = re.findall(pattern, smiles)
|
| 27 |
+
return set(elements)
|
| 28 |
+
|
| 29 |
+
def main():
|
| 30 |
+
if len(sys.argv) != 2:
|
| 31 |
+
print("Usage: python check_explicit_hydrogens.py <input_file>")
|
| 32 |
+
sys.exit(1)
|
| 33 |
+
|
| 34 |
+
input_file = sys.argv[1]
|
| 35 |
+
|
| 36 |
+
try:
|
| 37 |
+
with open(input_file, 'r') as f:
|
| 38 |
+
lines = f.readlines()
|
| 39 |
+
except FileNotFoundError:
|
| 40 |
+
print(f"Error: File {input_file} not found")
|
| 41 |
+
sys.exit(1)
|
| 42 |
+
|
| 43 |
+
# Skip header if present
|
| 44 |
+
if lines and lines[0].strip().lower() in ['rxn', 'reaction', 'smiles']:
|
| 45 |
+
lines = lines[1:]
|
| 46 |
+
|
| 47 |
+
total_reactions = len(lines)
|
| 48 |
+
reactions_with_explicit_h = []
|
| 49 |
+
reactions_with_explicit_h_and_metals = []
|
| 50 |
+
reactions_with_explicit_h_no_metals = []
|
| 51 |
+
metal_counter = Counter()
|
| 52 |
+
|
| 53 |
+
print("Analyzing reactions...")
|
| 54 |
+
print("=" * 60)
|
| 55 |
+
|
| 56 |
+
for line in lines:
|
| 57 |
+
line = line.strip()
|
| 58 |
+
if not line:
|
| 59 |
+
continue
|
| 60 |
+
|
| 61 |
+
if has_explicit_hydrogens(line):
|
| 62 |
+
reactions_with_explicit_h.append(line)
|
| 63 |
+
elements = extract_elements(line)
|
| 64 |
+
metals_found = elements & METALS
|
| 65 |
+
|
| 66 |
+
if metals_found:
|
| 67 |
+
reactions_with_explicit_h_and_metals.append(line)
|
| 68 |
+
for metal in metals_found:
|
| 69 |
+
metal_counter[metal] += 1
|
| 70 |
+
else:
|
| 71 |
+
reactions_with_explicit_h_no_metals.append(line)
|
| 72 |
+
|
| 73 |
+
# Print statistics
|
| 74 |
+
print(f"\nTotal reactions: {total_reactions}")
|
| 75 |
+
print(f"Reactions with explicit hydrogens: {len(reactions_with_explicit_h)}")
|
| 76 |
+
print(f" - With organometallic elements: {len(reactions_with_explicit_h_and_metals)}")
|
| 77 |
+
print(f" - Without organometallic elements: {len(reactions_with_explicit_h_no_metals)}")
|
| 78 |
+
|
| 79 |
+
if reactions_with_explicit_h:
|
| 80 |
+
percentage = (len(reactions_with_explicit_h_and_metals) / len(reactions_with_explicit_h)) * 100
|
| 81 |
+
print(f"\nPercentage with metals: {percentage:.2f}%")
|
| 82 |
+
|
| 83 |
+
# Show metal breakdown
|
| 84 |
+
if metal_counter:
|
| 85 |
+
print("\nBreakdown by metal type (for reactions with explicit H):")
|
| 86 |
+
print("=" * 60)
|
| 87 |
+
for metal, count in metal_counter.most_common():
|
| 88 |
+
print(f" {metal}: {count} reactions")
|
| 89 |
+
|
| 90 |
+
# Show examples without metals
|
| 91 |
+
if reactions_with_explicit_h_no_metals:
|
| 92 |
+
print(f"\nFirst 10 examples with explicit H but NO metals:")
|
| 93 |
+
print("=" * 60)
|
| 94 |
+
for i, rxn in enumerate(reactions_with_explicit_h_no_metals[:10], 1):
|
| 95 |
+
# Truncate long reactions for display
|
| 96 |
+
display_rxn = rxn if len(rxn) <= 100 else rxn[:97] + "..."
|
| 97 |
+
print(f"{i}. {display_rxn}")
|
| 98 |
+
|
| 99 |
+
# Additional analysis: check for specific patterns
|
| 100 |
+
print("\n\nAdditional Analysis:")
|
| 101 |
+
print("=" * 60)
|
| 102 |
+
|
| 103 |
+
# Check for radicals (indicated by explicit H on single atoms)
|
| 104 |
+
radical_pattern = r'\[CH?\](?![0-9])' # [C] or [CH] not followed by a digit
|
| 105 |
+
radicals = [rxn for rxn in reactions_with_explicit_h_no_metals if re.search(radical_pattern, rxn)]
|
| 106 |
+
print(f"Potential radicals (no metals): {len(radicals)}")
|
| 107 |
+
|
| 108 |
+
# Check for charged species
|
| 109 |
+
charged_pattern = r'\[CH\d?[+-]\]'
|
| 110 |
+
charged = [rxn for rxn in reactions_with_explicit_h_no_metals if re.search(charged_pattern, rxn)]
|
| 111 |
+
print(f"Charged species with explicit H (no metals): {len(charged)}")
|
| 112 |
+
|
| 113 |
+
if radicals and len(radicals) <= 5:
|
| 114 |
+
print("\nExamples of potential radicals:")
|
| 115 |
+
for rxn in radicals[:5]:
|
| 116 |
+
display_rxn = rxn if len(rxn) <= 100 else rxn[:97] + "..."
|
| 117 |
+
print(f" {display_rxn}")
|
| 118 |
+
|
| 119 |
+
if __name__ == "__main__":
|
| 120 |
+
main()
|
| 121 |
+
|
| 122 |
+
# Made with Bob
|
model-training/tools/analyze_tokens.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Analyse token lengths across the train/test/validation JSONL splits.
|
| 3 |
+
|
| 4 |
+
Reports the maximum token count for source and target sequences and how
|
| 5 |
+
many samples exceed the configured max_length threshold.
|
| 6 |
+
|
| 7 |
+
Usage:
|
| 8 |
+
python analyze_tokens.py --data-dir data/pistachio/data.forward --vocab vocab/vocab.txt
|
| 9 |
+
python analyze_tokens.py --data-dir data/pistachio/data.forward --vocab vocab/vocab.txt --max-length 512
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import argparse
|
| 13 |
+
import json
|
| 14 |
+
import os
|
| 15 |
+
import sys
|
| 16 |
+
|
| 17 |
+
# Allow running from any directory inside model-training/
|
| 18 |
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
| 19 |
+
|
| 20 |
+
from transformers_model.smiles_tokenizer import SmilesTokenizer
|
| 21 |
+
|
| 22 |
+
SPLITS = ["train", "test", "validation"]
|
| 23 |
+
BATCH_SIZE = 1000
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def analyse(data_dir: str, vocab_path: str, max_length: int) -> None:
|
| 27 |
+
tokenizer = SmilesTokenizer(vocab_path)
|
| 28 |
+
|
| 29 |
+
source_max_tokens = 0
|
| 30 |
+
target_max_tokens = 0
|
| 31 |
+
source_over_limit = 0
|
| 32 |
+
target_over_limit = 0
|
| 33 |
+
total_samples = 0
|
| 34 |
+
|
| 35 |
+
for split in SPLITS:
|
| 36 |
+
file_path = os.path.join(data_dir, f"{split}.jsonl")
|
| 37 |
+
if not os.path.exists(file_path):
|
| 38 |
+
print(f" [skip] {file_path} not found")
|
| 39 |
+
continue
|
| 40 |
+
|
| 41 |
+
print(f" Processing {file_path} ...")
|
| 42 |
+
with open(file_path, "r", encoding="utf-8") as f:
|
| 43 |
+
batch_sources = []
|
| 44 |
+
batch_targets = []
|
| 45 |
+
|
| 46 |
+
def process_batch(sources, targets):
|
| 47 |
+
nonlocal source_max_tokens, target_max_tokens
|
| 48 |
+
nonlocal source_over_limit, target_over_limit
|
| 49 |
+
|
| 50 |
+
source_encodings = tokenizer(sources, add_special_tokens=True, truncation=False)
|
| 51 |
+
target_encodings = tokenizer(targets, add_special_tokens=True, truncation=False)
|
| 52 |
+
|
| 53 |
+
for s_ids, t_ids in zip(source_encodings["input_ids"], target_encodings["input_ids"]):
|
| 54 |
+
s_len = len(s_ids)
|
| 55 |
+
t_len = len(t_ids)
|
| 56 |
+
source_max_tokens = max(source_max_tokens, s_len)
|
| 57 |
+
target_max_tokens = max(target_max_tokens, t_len)
|
| 58 |
+
if s_len > max_length:
|
| 59 |
+
source_over_limit += 1
|
| 60 |
+
if t_len > max_length:
|
| 61 |
+
target_over_limit += 1
|
| 62 |
+
|
| 63 |
+
for line in f:
|
| 64 |
+
data = json.loads(line)
|
| 65 |
+
batch_sources.append(data["source"])
|
| 66 |
+
batch_targets.append(data["target"])
|
| 67 |
+
total_samples += 1
|
| 68 |
+
|
| 69 |
+
if len(batch_sources) == BATCH_SIZE:
|
| 70 |
+
process_batch(batch_sources, batch_targets)
|
| 71 |
+
batch_sources = []
|
| 72 |
+
batch_targets = []
|
| 73 |
+
|
| 74 |
+
if batch_sources:
|
| 75 |
+
process_batch(batch_sources, batch_targets)
|
| 76 |
+
|
| 77 |
+
print()
|
| 78 |
+
print(f"Max length threshold : {max_length}")
|
| 79 |
+
print(f"Max tokens in source : {source_max_tokens}")
|
| 80 |
+
print(f"Max tokens in target : {target_max_tokens}")
|
| 81 |
+
print(f"Source over limit : {source_over_limit} / {total_samples} ({(source_over_limit / total_samples) * 100:.2f}%)" if total_samples else "No samples processed.")
|
| 82 |
+
print(f"Target over limit : {target_over_limit} / {total_samples} ({(target_over_limit / total_samples) * 100:.2f}%)" if total_samples else "")
|
| 83 |
+
print(f"Total samples : {total_samples}")
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
if __name__ == "__main__":
|
| 87 |
+
parser = argparse.ArgumentParser(description="Analyse token lengths in JSONL splits.")
|
| 88 |
+
parser.add_argument(
|
| 89 |
+
"--data-dir",
|
| 90 |
+
required=True,
|
| 91 |
+
help="Directory containing train.jsonl, test.jsonl, validation.jsonl "
|
| 92 |
+
"(e.g. data/pistachio/data.forward)",
|
| 93 |
+
)
|
| 94 |
+
parser.add_argument(
|
| 95 |
+
"--vocab",
|
| 96 |
+
required=True,
|
| 97 |
+
help="Path to vocab.txt (e.g. data/pistachio/vocab.txt)",
|
| 98 |
+
)
|
| 99 |
+
parser.add_argument(
|
| 100 |
+
"--max-length",
|
| 101 |
+
type=int,
|
| 102 |
+
default=256,
|
| 103 |
+
help="Token length threshold to count over-limit samples (default: 256)",
|
| 104 |
+
)
|
| 105 |
+
args = parser.parse_args()
|
| 106 |
+
analyse(args.data_dir, args.vocab, args.max_length)
|
model-training/tools/check_mol_from_smiles.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from rdkit import Chem, DataStructs
|
| 2 |
+
from rdkit.Chem.rdFingerprintGenerator import GetMorganGenerator
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
smiles = "O=S(=O)([O-])c1ccccc1~O=S(=O)([O-])c1ccccc1~O=S(=O)([O-])c1ccccc1~O=S(=O)([O-])c1ccccc1~O=S(=O)([O-])c1ccccc1~[Fe+2]~[Fe+2]~[Fe+2]~[Fe+2]~[Fe+2]~[Fe+2]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~[Fe+3]~"
|
| 6 |
+
|
| 7 |
+
if __name__ == "__main__":
|
| 8 |
+
print(f"Smiles: {smiles}")
|
| 9 |
+
mol1 = Chem.MolFromSmiles(smiles)
|
| 10 |
+
print(f"Molecule: {mol1}")
|
model-training/tools/check_tanimoto.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from rdkit import Chem, DataStructs
|
| 2 |
+
from rdkit.Chem.rdFingerprintGenerator import GetMorganGenerator
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
predicted_texts = [
|
| 6 |
+
"",
|
| 7 |
+
]
|
| 8 |
+
|
| 9 |
+
target_texts = [
|
| 10 |
+
"",
|
| 11 |
+
]
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
if __name__ == "__main__":
|
| 15 |
+
similarities = []
|
| 16 |
+
invalid_pairs = 0
|
| 17 |
+
generator = GetMorganGenerator(radius=2, fpSize=2048)
|
| 18 |
+
for pred, tgt in zip(predicted_texts, target_texts):
|
| 19 |
+
|
| 20 |
+
# Get molecule from prediction
|
| 21 |
+
print(f"Predicted smiles (pred): {pred}")
|
| 22 |
+
mol1 = Chem.MolFromSmiles(pred)
|
| 23 |
+
print(f"Predicted molecule (mol1): {mol1}")
|
| 24 |
+
|
| 25 |
+
# Get molecule from ground truth
|
| 26 |
+
print(f"Target smiles (tgt): {tgt}")
|
| 27 |
+
mol2 = Chem.MolFromSmiles(tgt)
|
| 28 |
+
print(f"Target molecule (mol2): {mol2}")
|
| 29 |
+
|
| 30 |
+
# Increment invalid pairs on failure
|
| 31 |
+
if mol1 is None or mol2 is None:
|
| 32 |
+
invalid_pairs += 1
|
| 33 |
+
print(f"Invalid pair! Total: {invalid_pairs}")
|
| 34 |
+
continue
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
fp1 = generator.GetFingerprint(mol1)
|
| 38 |
+
print(f"Predicted fingerprint (fp1): {fp1}")
|
| 39 |
+
fp2 = generator.GetFingerprint(mol2)
|
| 40 |
+
print(f"Target fingerprint (fp2): {fp2}")
|
| 41 |
+
sim = DataStructs.TanimotoSimilarity(fp1, fp2)
|
| 42 |
+
print(f"Similarity (sim): {sim}")
|
| 43 |
+
similarities.append(sim)
|
| 44 |
+
|
| 45 |
+
avg_tanimoto = sum(similarities) / len(similarities) if len(similarities) > 0 else 0
|
| 46 |
+
print(f"Average Tanimoto (avg_tanimoto): {avg_tanimoto}")
|
model-training/tools/run_fit.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Train the model directly with Lightning, bypassing LightningCLI.
|
| 3 |
+
|
| 4 |
+
Useful for quick experiments where you want to set options in code rather
|
| 5 |
+
than via a YAML config. For production training use cli_main.py with the
|
| 6 |
+
YAML configs in training/.
|
| 7 |
+
|
| 8 |
+
Usage (run from model-training/):
|
| 9 |
+
python debug/run_fit.py
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import os
|
| 13 |
+
import sys
|
| 14 |
+
|
| 15 |
+
# Allow running from any directory inside model-training/
|
| 16 |
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
| 17 |
+
|
| 18 |
+
import lightning as L
|
| 19 |
+
|
| 20 |
+
from transformers_model.smiles_datamodule import LitSmilesDataset
|
| 21 |
+
from transformers_model.model import LitVanillaTransformer
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
if __name__ == "__main__":
|
| 25 |
+
L.seed_everything(42)
|
| 26 |
+
|
| 27 |
+
smiles_dataset = LitSmilesDataset(
|
| 28 |
+
vocab_path="vocab/vocab.txt",
|
| 29 |
+
train_path="data/pistachio/data.forward/train.jsonl",
|
| 30 |
+
validation_path="data/pistachio/data.forward/validation.jsonl",
|
| 31 |
+
num_dataloader_workers=16,
|
| 32 |
+
batch_size=256,
|
| 33 |
+
max_length=256,
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
model = LitVanillaTransformer(
|
| 37 |
+
vocab_path="vocab/vocab.txt",
|
| 38 |
+
task="forward",
|
| 39 |
+
max_length=256,
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
trainer = L.Trainer(
|
| 43 |
+
max_epochs=10,
|
| 44 |
+
accelerator="gpu",
|
| 45 |
+
devices=1,
|
| 46 |
+
precision=32,
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
trainer.fit(model, datamodule=smiles_dataset)
|
| 50 |
+
print("Training complete.")
|
model-training/tools/run_lr_finder.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Runs the Lightning learning rate finder and saves a plot and CSV of the results.
|
| 3 |
+
|
| 4 |
+
Output files are written to the same directory as this script:
|
| 5 |
+
- lr_finder_plot.png
|
| 6 |
+
- lr_finder_results.csv
|
| 7 |
+
|
| 8 |
+
Usage (run from model-training/):
|
| 9 |
+
python debug/run_lr_finder.py
|
| 10 |
+
python debug/run_lr_finder.py --data-dir data/pistachio/data.forward --vocab vocab/vocab.txt
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import argparse
|
| 14 |
+
import csv
|
| 15 |
+
import os
|
| 16 |
+
import sys
|
| 17 |
+
import torch
|
| 18 |
+
|
| 19 |
+
# Allow running from any directory inside model-training/
|
| 20 |
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
| 21 |
+
|
| 22 |
+
import lightning as L
|
| 23 |
+
import matplotlib.pyplot as plt
|
| 24 |
+
from lightning.pytorch.tuner import Tuner
|
| 25 |
+
|
| 26 |
+
from transformers_model.smiles_datamodule import LitSmilesDataset
|
| 27 |
+
from transformers_model.model import LitVanillaTransformer
|
| 28 |
+
|
| 29 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def run(data_dir: str, vocab_path: str) -> None:
|
| 33 |
+
L.seed_everything(42)
|
| 34 |
+
torch.set_float32_matmul_precision("high")
|
| 35 |
+
|
| 36 |
+
smiles_dataset = LitSmilesDataset(
|
| 37 |
+
vocab_path=vocab_path,
|
| 38 |
+
train_path=os.path.join(data_dir, "train.jsonl"),
|
| 39 |
+
validation_path=os.path.join(data_dir, "validation.jsonl"),
|
| 40 |
+
num_dataloader_workers=16,
|
| 41 |
+
batch_size=1024,
|
| 42 |
+
max_length=256,
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
model = LitVanillaTransformer(
|
| 46 |
+
vocab_path=vocab_path,
|
| 47 |
+
task="forward",
|
| 48 |
+
max_length=256,
|
| 49 |
+
ignore_nan=True,
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
trainer = L.Trainer(
|
| 53 |
+
max_epochs=60,
|
| 54 |
+
accelerator="gpu",
|
| 55 |
+
devices=1,
|
| 56 |
+
precision="16-mixed",
|
| 57 |
+
deterministic=True,
|
| 58 |
+
gradient_clip_val=1.0,
|
| 59 |
+
gradient_clip_algorithm="norm",
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
tuner = Tuner(trainer)
|
| 63 |
+
lr_finder = tuner.lr_find(model, datamodule=smiles_dataset)
|
| 64 |
+
|
| 65 |
+
# Plot
|
| 66 |
+
fig = lr_finder.plot(suggest=True)
|
| 67 |
+
plt.grid(True, which="both", linestyle="--", linewidth=0.5)
|
| 68 |
+
plot_path = os.path.join(HERE, "lr_finder_plot.png")
|
| 69 |
+
fig.savefig(plot_path)
|
| 70 |
+
print(f"Plot saved to {plot_path}")
|
| 71 |
+
plt.show()
|
| 72 |
+
|
| 73 |
+
# Save results as CSV
|
| 74 |
+
csv_path = os.path.join(HERE, "lr_finder_results.csv")
|
| 75 |
+
with open(csv_path, "w", newline="") as f:
|
| 76 |
+
writer = csv.writer(f)
|
| 77 |
+
writer.writerow(["learning_rate", "loss"])
|
| 78 |
+
writer.writerows(zip(lr_finder.results["lr"], lr_finder.results["loss"]))
|
| 79 |
+
print(f"Results saved to {csv_path}")
|
| 80 |
+
|
| 81 |
+
print(f"Suggested learning rate: {lr_finder.suggestion()}")
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
if __name__ == "__main__":
|
| 85 |
+
parser = argparse.ArgumentParser(description="Run the Lightning LR finder.")
|
| 86 |
+
parser.add_argument(
|
| 87 |
+
"--data-dir",
|
| 88 |
+
default="data/pistachio/data.forward",
|
| 89 |
+
help="Directory containing train.jsonl and validation.jsonl "
|
| 90 |
+
"(default: data/pistachio/data.forward)",
|
| 91 |
+
)
|
| 92 |
+
parser.add_argument(
|
| 93 |
+
"--vocab",
|
| 94 |
+
default="vocab/vocab.txt",
|
| 95 |
+
help="Path to vocab.txt (default: vocab/vocab.txt)",
|
| 96 |
+
)
|
| 97 |
+
args = parser.parse_args()
|
| 98 |
+
run(args.data_dir, args.vocab)
|
model-training/tools/run_predict.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Run predictions with the model directly via Lightning, bypassing LightningCLI.
|
| 3 |
+
|
| 4 |
+
Useful for quick experiments where you want to set options in code rather
|
| 5 |
+
than via a YAML config. For production inference use cli_main.py with
|
| 6 |
+
training/predict.yaml.
|
| 7 |
+
|
| 8 |
+
Usage (run from model-training/):
|
| 9 |
+
python debug/run_predict.py
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import os
|
| 13 |
+
import sys
|
| 14 |
+
|
| 15 |
+
# Allow running from any directory inside model-training/
|
| 16 |
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
| 17 |
+
|
| 18 |
+
import lightning as L
|
| 19 |
+
|
| 20 |
+
from transformers_model.smiles_datamodule import LitSmilesDataset
|
| 21 |
+
from transformers_model.model import LitVanillaTransformer
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
if __name__ == "__main__":
|
| 25 |
+
L.seed_everything(42)
|
| 26 |
+
|
| 27 |
+
smiles_dataset = LitSmilesDataset(
|
| 28 |
+
vocab_path="vocab/vocab.txt",
|
| 29 |
+
predict_path="data/input.jsonl",
|
| 30 |
+
num_dataloader_workers=16,
|
| 31 |
+
batch_size=256,
|
| 32 |
+
max_length=256,
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
# Replace with the actual checkpoint path
|
| 36 |
+
model = LitVanillaTransformer.load_from_checkpoint(
|
| 37 |
+
checkpoint_path="lightning_logs/forward/checkpoints/epoch=59-val_accuracy=0.6476.ckpt",
|
| 38 |
+
vocab_path="vocab/vocab.txt",
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
trainer = L.Trainer(
|
| 42 |
+
accelerator="gpu",
|
| 43 |
+
devices=1,
|
| 44 |
+
precision=32,
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
trainer.predict(model, datamodule=smiles_dataset)
|
| 48 |
+
print("Prediction complete.")
|
model-training/tools/run_test.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Test the model directly with Lightning, bypassing LightningCLI.
|
| 3 |
+
|
| 4 |
+
Useful for quick experiments where you want to set options in code rather
|
| 5 |
+
than via a YAML config. For production testing use cli_main.py with the
|
| 6 |
+
YAML configs in training/.
|
| 7 |
+
|
| 8 |
+
Usage (run from model-training/):
|
| 9 |
+
python debug/run_test.py
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import os
|
| 13 |
+
import sys
|
| 14 |
+
|
| 15 |
+
# Allow running from any directory inside model-training/
|
| 16 |
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
| 17 |
+
|
| 18 |
+
import lightning as L
|
| 19 |
+
|
| 20 |
+
from transformers_model.smiles_datamodule import LitSmilesDataset
|
| 21 |
+
from transformers_model.model import LitVanillaTransformer
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
if __name__ == "__main__":
|
| 25 |
+
L.seed_everything(42)
|
| 26 |
+
|
| 27 |
+
smiles_dataset = LitSmilesDataset(
|
| 28 |
+
vocab_path="vocab/vocab.txt",
|
| 29 |
+
test_path="data/pistachio/data.forward/test.jsonl",
|
| 30 |
+
num_dataloader_workers=16,
|
| 31 |
+
batch_size=256,
|
| 32 |
+
max_length=256,
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
# Replace with the actual checkpoint path
|
| 36 |
+
model = LitVanillaTransformer.load_from_checkpoint(
|
| 37 |
+
checkpoint_path="lightning_logs/forward/checkpoints/epoch=59-val_accuracy=0.6476.ckpt",
|
| 38 |
+
vocab_path="vocab/vocab.txt",
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
trainer = L.Trainer(
|
| 42 |
+
accelerator="gpu",
|
| 43 |
+
devices=1,
|
| 44 |
+
precision=32,
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
trainer.test(model, datamodule=smiles_dataset)
|
| 48 |
+
print("Testing complete.")
|
model-training/training/cli_main.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
|
| 3 |
+
from lightning.pytorch.cli import LightningCLI
|
| 4 |
+
from transformers_model.model import LitVanillaTransformer
|
| 5 |
+
from transformers_model.smiles_datamodule import LitSmilesDataset
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def main():
|
| 9 |
+
|
| 10 |
+
# Enable Tensor Core optimization
|
| 11 |
+
torch.set_float32_matmul_precision('high')
|
| 12 |
+
|
| 13 |
+
LightningCLI(
|
| 14 |
+
model_class=LitVanillaTransformer,
|
| 15 |
+
datamodule_class=LitSmilesDataset,
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
if __name__ == "__main__":
|
| 20 |
+
main()
|
model-training/training/predict.yaml
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
model:
|
| 2 |
+
vocab_path: vocab/vocab.txt
|
| 3 |
+
task: "forward"
|
| 4 |
+
predictions_path: "predictions.jsonl"
|
| 5 |
+
num_beams: 3
|
| 6 |
+
topn: 3
|
| 7 |
+
max_length: 256 # for generating predictions
|
| 8 |
+
data:
|
| 9 |
+
vocab_path: vocab/vocab.txt
|
| 10 |
+
predict_path: data/input.jsonl
|
| 11 |
+
truncation: true
|
| 12 |
+
max_length: 256 # for tokenizing input data
|
| 13 |
+
num_dataloader_workers: 16
|
| 14 |
+
batch_size: 512
|
| 15 |
+
seed_everything: 42
|
| 16 |
+
ckpt_path: lightning_logs/forward/checkpoints/epoch=59-val_accuracy=0.6476.ckpt
|
model-training/training/test.yaml
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
model:
|
| 2 |
+
vocab_path: vocab/vocab.txt
|
| 3 |
+
task: "forward"
|
| 4 |
+
learning_rate: 0.001584893192461114
|
| 5 |
+
warmup_ratio: 0.1
|
| 6 |
+
# attention_mask: -1e4
|
| 7 |
+
max_length: 256 # for generating predictions
|
| 8 |
+
data:
|
| 9 |
+
vocab_path: vocab/vocab.txt
|
| 10 |
+
test_path: data/pistachio/data.forward/test.jsonl
|
| 11 |
+
truncation: true
|
| 12 |
+
max_length: 256 # for tokenizing input data
|
| 13 |
+
num_dataloader_workers: 16
|
| 14 |
+
batch_size: 512
|
| 15 |
+
trainer:
|
| 16 |
+
accumulate_grad_batches: 2
|
| 17 |
+
max_epochs: 60
|
| 18 |
+
# profiler: simple
|
| 19 |
+
accelerator: gpu
|
| 20 |
+
enable_progress_bar: false
|
| 21 |
+
devices: 1
|
| 22 |
+
precision: 32
|
| 23 |
+
deterministic: true
|
| 24 |
+
# detect_anomaly: true
|
| 25 |
+
gradient_clip_val: 1.0
|
| 26 |
+
gradient_clip_algorithm: "norm"
|
| 27 |
+
callbacks:
|
| 28 |
+
- class_path: lightning.pytorch.callbacks.ModelCheckpoint
|
| 29 |
+
init_args:
|
| 30 |
+
monitor: val_accuracy
|
| 31 |
+
mode: max
|
| 32 |
+
save_top_k: 10
|
| 33 |
+
filename: "{epoch:02d}-{val_accuracy:.4f}"
|
| 34 |
+
seed_everything: 42
|
| 35 |
+
ckpt_path: lightning_logs/forward/checkpoints/epoch=59-val_accuracy=0.6476.ckpt
|