I'm not sure if this should be a PR since it's not an essential feature, so I'm documenting the feature here as an issue.
Example visualizations
A reaction tree is the tree representation of a chemical synthesis route, with the root node being the target molecule, which is always printed on the first line.
Tamoxifen
CC/C(=C(\c1ccccc1)c1ccc(OCCN(C)C)cc1)c1ccccc1
├── CC/C(=C\c1ccccc1)c1ccccc1
│ └── CCC(O)(Cc1ccccc1)c1ccccc1
│ ├── CCC(=O)Cc1ccccc1
│ └── Brc1ccccc1
└── CN(C)CCOc1ccc(I)cc1
├── CN(C)CCCl
└── Oc1ccc(I)cc1
Ritonavir
CC(C)c1nc(CN(C)C(=O)N[C@H](C(=O)N[C@@H](Cc2ccccc2)C[C@H](O)[C@H](Cc2ccccc2)NC(=O)OCc2cncs2)C(C)C)cs1
├── CC(C)c1nc(CN(C)C(=O)N[C@H](C(=O)Cl)C(C)C)cs1
│ ├── O=C(Cl)C(=O)Cl
│ └── CC(C)c1nc(CN(C)C(=O)N[C@H](C(=O)O)C(C)C)cs1
│ └── COC(=O)[C@@H](NC(=O)N(C)Cc1csc(C(C)C)n1)C(C)C
│ ├── CNCc1csc(C(C)C)n1
│ └── COC(=O)[C@@H](NC(=O)Oc1ccc([N+](=O)[O-])cc1)C(C)C
│ ├── COC(=O)[C@@H](N)C(C)C
│ └── O=C(Cl)Oc1ccc([N+](=O)[O-])cc1
└── N[C@@H](Cc1ccccc1)C[C@H](O)[C@H](Cc1ccccc1)NC(=O)OCc1cncs1
└── [N-]=[N+]=N[C@@H](Cc1ccccc1)C[C@H](O)[C@H](Cc1ccccc1)NC(=O)OCc1cncs1
└── CC(C)(C)[Si](C)(C)O[C@@H](C[C@H](Cc1ccccc1)N=[N+]=[N-])[C@H](Cc1ccccc1)NC(=O)OCc1cncs1
├── O=C(OCc1cncs1)Oc1ccc([N+](=O)[O-])cc1
│ ├── OCc1cncs1
│ └── O=C(Cl)Oc1ccc([N+](=O)[O-])cc1
└── CC(C)(C)[Si](C)(C)O[C@@H](C[C@H](Cc1ccccc1)N=[N+]=[N-])[C@@H](N)Cc1ccccc1
In-house implementation
The in-house code is fairly simple, as shown below.
How to reproduce
Add this function to the ReactionPath class:
def show_tree(self, stdout: bool = True) -> str | None:
# Outer, nonlocal string to be modified during depth-first search
# In this string, the first line is always the root node
string = self.root.smiles + '\n'
# Depth-first search
def dfs(curr: Node, prefix: str):
nonlocal string
for i in range(len(curr.children)):
node_id = curr.children[i]
if i == len(curr.children) - 1:
string += prefix + '└── ' + self.nodes[node_id].smiles + '\n'
dfs(self.nodes[node_id], prefix + ' ')
else:
string += prefix + '├── ' + self.nodes[node_id].smiles + '\n'
dfs(self.nodes[node_id], prefix + '│ ')
dfs(self.root, '')
# Whether to print to console or return the string object
if stdout:
print(string)
else:
return string
Then, run the following:
from charge.servers.AiZynthTools import find_synthesis_routes
routes = find_synthesis_routes('CN(C)CCOc1ccc(cc1)/C(c2ccccc2)=C(/CC)c3ccccc3') # tamoxifen
path = ReactionPath(routes[0])
path.show_tree()
treelib implementation
If we want more features for reaction tree analysis and visualization, I recommend using the treelib package, which is very light-weight and only requires the six dependency.
How to reproduce
from treelib import Tree
from treelib.node import Node
def route2tree(route: dict) -> Tree:
tree = Tree()
def dfs(curr: dict, parent: Node | None):
if curr['type'] == 'reaction':
for child in curr['children']:
dfs(child, parent)
else:
# You can add custom data (such as `in_stock`) with the `data` keyword
node = tree.create_node(tag=curr['smiles'], parent=parent, data={'in_stock': curr['in_stock']})
if 'children' in curr:
for child in curr['children']:
dfs(child, node)
dfs(route, None)
return tree
routes = find_synthesis_routes('CN(C)CCOc1ccc(cc1)/C(c2ccccc2)=C(/CC)c3ccccc3') # tamoxifen
tree = route2tree(routes[0])
tree.show()
I'm not sure if this should be a PR since it's not an essential feature, so I'm documenting the feature here as an issue.
Example visualizations
A reaction tree is the tree representation of a chemical synthesis route, with the root node being the target molecule, which is always printed on the first line.
Tamoxifen
Ritonavir
In-house implementation
The in-house code is fairly simple, as shown below.
How to reproduce
Add this function to the
ReactionPathclass:Then, run the following:
treelibimplementationIf we want more features for reaction tree analysis and visualization, I recommend using the
treelibpackage, which is very light-weight and only requires thesixdependency.How to reproduce