torch-molecule 0.1.0.dev2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- torch_molecule/__init__.py +70 -0
- torch_molecule/base/__init__.py +8 -0
- torch_molecule/base/base.py +396 -0
- torch_molecule/base/encoder.py +44 -0
- torch_molecule/base/generator.py +24 -0
- torch_molecule/base/old_encoder.py +322 -0
- torch_molecule/base/old_predictor.py +904 -0
- torch_molecule/base/old_predictor2.py +436 -0
- torch_molecule/base/predictor.py +112 -0
- torch_molecule/encoder/attrmask/__init__.py +3 -0
- torch_molecule/encoder/attrmask/model.py +113 -0
- torch_molecule/encoder/attrmask/modeling_attrmask.py +304 -0
- torch_molecule/encoder/constant.py +13 -0
- torch_molecule/encoder/contextpred/__init__.py +3 -0
- torch_molecule/encoder/contextpred/model.py +207 -0
- torch_molecule/encoder/contextpred/modeling_contextpred.py +299 -0
- torch_molecule/encoder/contextpred/utils.py +206 -0
- torch_molecule/encoder/edgepred/__init__.py +3 -0
- torch_molecule/encoder/edgepred/model.py +104 -0
- torch_molecule/encoder/edgepred/modeling_edgepred.py +285 -0
- torch_molecule/encoder/graphmae/__init__.py +3 -0
- torch_molecule/encoder/graphmae/dataloader.py +206 -0
- torch_molecule/encoder/graphmae/model.py +137 -0
- torch_molecule/encoder/graphmae/modeling_graphmae.py +323 -0
- torch_molecule/encoder/infograph/__init__.py +3 -0
- torch_molecule/encoder/infograph/loss_function.py +128 -0
- torch_molecule/encoder/infograph/model.py +92 -0
- torch_molecule/encoder/infograph/modeling_infograph.py +301 -0
- torch_molecule/encoder/infograph/module.py +31 -0
- torch_molecule/encoder/moama/__init__.py +3 -0
- torch_molecule/encoder/moama/model.py +131 -0
- torch_molecule/encoder/moama/modeling_moama.py +307 -0
- torch_molecule/encoder/moama/utils.py +143 -0
- torch_molecule/encoder/pretrained/__init__.py +3 -0
- torch_molecule/encoder/pretrained/modeling_pretrained.py +274 -0
- torch_molecule/encoder/supervised/__init__.py +3 -0
- torch_molecule/encoder/supervised/model.py +97 -0
- torch_molecule/encoder/supervised/modeling_supervised.py +354 -0
- torch_molecule/generator/digress/__init__.py +3 -0
- torch_molecule/generator/digress/diffusion.py +194 -0
- torch_molecule/generator/digress/modeling_digress.py +503 -0
- torch_molecule/generator/digress/transformer.py +360 -0
- torch_molecule/generator/digress/utils.py +196 -0
- torch_molecule/generator/gdss/__init__.py +3 -0
- torch_molecule/generator/gdss/layers.py +257 -0
- torch_molecule/generator/gdss/model.py +204 -0
- torch_molecule/generator/gdss/modeling_gdss.py +474 -0
- torch_molecule/generator/gdss/sde.py +334 -0
- torch_molecule/generator/gdss/solver.py +227 -0
- torch_molecule/generator/gdss/toremove/ScoreNetworkA.py +167 -0
- torch_molecule/generator/gdss/toremove/ScoreNetworkX.py +85 -0
- torch_molecule/generator/gdss/utils.py +240 -0
- torch_molecule/generator/graph_dit/__init__.py +3 -0
- torch_molecule/generator/graph_dit/diffusion.py +188 -0
- torch_molecule/generator/graph_dit/modeling_graph_dit.py +581 -0
- torch_molecule/generator/graph_dit/transformer.py +207 -0
- torch_molecule/generator/graph_dit/utils.py +220 -0
- torch_molecule/generator/graph_ga/__init__.py +3 -0
- torch_molecule/generator/graph_ga/crossover.py +182 -0
- torch_molecule/generator/graph_ga/modeling_graph_ga.py +310 -0
- torch_molecule/generator/graph_ga/mutate.py +136 -0
- torch_molecule/generator/graph_ga/oracle.py +95 -0
- torch_molecule/generator/jtvae/__init__.py +3 -0
- torch_molecule/generator/jtvae/jtnn/chemutils.py +450 -0
- torch_molecule/generator/jtvae/jtnn/datautils.py +127 -0
- torch_molecule/generator/jtvae/jtnn/jtmpn.py +140 -0
- torch_molecule/generator/jtvae/jtnn/jtnn_dec.py +350 -0
- torch_molecule/generator/jtvae/jtnn/jtnn_enc.py +131 -0
- torch_molecule/generator/jtvae/jtnn/mol_tree.py +111 -0
- torch_molecule/generator/jtvae/jtnn/mpn.py +129 -0
- torch_molecule/generator/jtvae/jtnn/nnutils.py +66 -0
- torch_molecule/generator/jtvae/jtnn/vocab.py +31 -0
- torch_molecule/generator/jtvae/jtnn_vae.py +246 -0
- torch_molecule/generator/jtvae/modeling_jtvae.py +247 -0
- torch_molecule/generator/lstm/__init__.py +3 -0
- torch_molecule/generator/lstm/action_sampler.py +91 -0
- torch_molecule/generator/lstm/lstm.py +59 -0
- torch_molecule/generator/lstm/modeling_lstm.py +263 -0
- torch_molecule/generator/lstm/smiles_char_dict.py +123 -0
- torch_molecule/generator/lstm/utils.py +62 -0
- torch_molecule/generator/molgpt/__init__.py +3 -0
- torch_molecule/generator/molgpt/dataset.py +87 -0
- torch_molecule/generator/molgpt/gpt.py +251 -0
- torch_molecule/generator/molgpt/modeling_molgpt.py +427 -0
- torch_molecule/generator/pretrained/modeling_pretrained.py +1 -0
- torch_molecule/nn/__init__.py +16 -0
- torch_molecule/nn/attention.py +63 -0
- torch_molecule/nn/embedder.py +183 -0
- torch_molecule/nn/gnn.py +631 -0
- torch_molecule/nn/mlp.py +34 -0
- torch_molecule/predictor/bfgnn/__init__.py +3 -0
- torch_molecule/predictor/bfgnn/model.py +116 -0
- torch_molecule/predictor/bfgnn/modeling_bfgnn.py +81 -0
- torch_molecule/predictor/dir/__init__.py +3 -0
- torch_molecule/predictor/dir/model.py +235 -0
- torch_molecule/predictor/dir/modeling_dir.py +161 -0
- torch_molecule/predictor/dir/utils.py +40 -0
- torch_molecule/predictor/gnn/__init__.py +3 -0
- torch_molecule/predictor/gnn/model.py +114 -0
- torch_molecule/predictor/gnn/modeling_gnn.py +706 -0
- torch_molecule/predictor/gnn/old_modeling_gnn.py +707 -0
- torch_molecule/predictor/grea/__init__.py +3 -0
- torch_molecule/predictor/grea/model.py +190 -0
- torch_molecule/predictor/grea/modeling_grea.py +107 -0
- torch_molecule/predictor/grea/old1_features.py +229 -0
- torch_molecule/predictor/grea/old1_modeling_grea.py +493 -0
- torch_molecule/predictor/grea/utils.py +97 -0
- torch_molecule/predictor/grin/__init__.py +3 -0
- torch_molecule/predictor/grin/model.py +116 -0
- torch_molecule/predictor/grin/modeling_grin.py +91 -0
- torch_molecule/predictor/irm/__init__.py +3 -0
- torch_molecule/predictor/irm/model.py +127 -0
- torch_molecule/predictor/irm/modeling_irm.py +179 -0
- torch_molecule/predictor/lstm/__init__.py +3 -0
- torch_molecule/predictor/lstm/model.py +145 -0
- torch_molecule/predictor/lstm/modeling_lstm.py +627 -0
- torch_molecule/predictor/lstm/token_from_smiles.py +85 -0
- torch_molecule/predictor/old_modeling_graph.py +650 -0
- torch_molecule/predictor/rpgnn/__init__.py +3 -0
- torch_molecule/predictor/rpgnn/model.py +185 -0
- torch_molecule/predictor/rpgnn/modeling_rpgnn.py +76 -0
- torch_molecule/predictor/sgir/__init__.py +3 -0
- torch_molecule/predictor/sgir/modeling_sgir.py +260 -0
- torch_molecule/predictor/sgir/strategy.py +266 -0
- torch_molecule/predictor/smiles_transformer/__init__.py +3 -0
- torch_molecule/predictor/smiles_transformer/model.py +424 -0
- torch_molecule/predictor/smiles_transformer/modeling_transformer.py +252 -0
- torch_molecule/predictor/ssr/__init__.py +3 -0
- torch_molecule/predictor/ssr/model.py +283 -0
- torch_molecule/predictor/ssr/modeling_ssr.py +267 -0
- torch_molecule/predictor/ssr/utils.py +185 -0
- torch_molecule/utils/__init__.py +31 -0
- torch_molecule/utils/checker.py +158 -0
- torch_molecule/utils/checkpoint.py +294 -0
- torch_molecule/utils/format.py +140 -0
- torch_molecule/utils/generic/metrics.py +530 -0
- torch_molecule/utils/generic/pseudo_tasks.py +11 -0
- torch_molecule/utils/generic/weights.py +30 -0
- torch_molecule/utils/graph/features.py +178 -0
- torch_molecule/utils/graph/graph_from_smiles.py +134 -0
- torch_molecule/utils/graph/graph_to_smiles.py +312 -0
- torch_molecule/utils/hf.py +232 -0
- torch_molecule/utils/search.py +64 -0
- torch_molecule-0.1.0.dev2.dist-info/METADATA +243 -0
- torch_molecule-0.1.0.dev2.dist-info/RECORD +148 -0
- torch_molecule-0.1.0.dev2.dist-info/WHEEL +5 -0
- torch_molecule-0.1.0.dev2.dist-info/licenses/LICENSE +21 -0
- torch_molecule-0.1.0.dev2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import rdkit
|
|
2
|
+
import rdkit.Chem as Chem
|
|
3
|
+
from scipy.sparse import csr_matrix
|
|
4
|
+
from scipy.sparse.csgraph import minimum_spanning_tree
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from rdkit.Chem.EnumerateStereoisomers import EnumerateStereoisomers, StereoEnumerationOptions
|
|
7
|
+
try:
|
|
8
|
+
from vocab import Vocab
|
|
9
|
+
except:
|
|
10
|
+
from .vocab import Vocab
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
|
|
14
|
+
MST_MAX_WEIGHT = 100
|
|
15
|
+
MAX_NCAND = 2000
|
|
16
|
+
|
|
17
|
+
def set_atommap(mol, num=0):
|
|
18
|
+
for atom in mol.GetAtoms():
|
|
19
|
+
atom.SetAtomMapNum(num)
|
|
20
|
+
|
|
21
|
+
def get_mol(smiles):
|
|
22
|
+
mol = Chem.MolFromSmiles(smiles)
|
|
23
|
+
if mol is None:
|
|
24
|
+
return None
|
|
25
|
+
Chem.Kekulize(mol)
|
|
26
|
+
return mol
|
|
27
|
+
|
|
28
|
+
def get_smiles(mol):
|
|
29
|
+
return Chem.MolToSmiles(mol, kekuleSmiles=True)
|
|
30
|
+
|
|
31
|
+
def decode_stereo(smiles2D):
|
|
32
|
+
mol = Chem.MolFromSmiles(smiles2D)
|
|
33
|
+
dec_isomers = list(EnumerateStereoisomers(mol))
|
|
34
|
+
|
|
35
|
+
dec_isomers = [Chem.MolFromSmiles(Chem.MolToSmiles(mol, isomericSmiles=True)) for mol in dec_isomers]
|
|
36
|
+
smiles3D = [Chem.MolToSmiles(mol, isomericSmiles=True) for mol in dec_isomers]
|
|
37
|
+
|
|
38
|
+
chiralN = [atom.GetIdx() for atom in dec_isomers[0].GetAtoms() if int(atom.GetChiralTag()) > 0 and atom.GetSymbol() == "N"]
|
|
39
|
+
if len(chiralN) > 0:
|
|
40
|
+
for mol in dec_isomers:
|
|
41
|
+
for idx in chiralN:
|
|
42
|
+
mol.GetAtomWithIdx(idx).SetChiralTag(Chem.rdchem.ChiralType.CHI_UNSPECIFIED)
|
|
43
|
+
smiles3D.append(Chem.MolToSmiles(mol, isomericSmiles=True))
|
|
44
|
+
|
|
45
|
+
return smiles3D
|
|
46
|
+
|
|
47
|
+
def sanitize(mol):
|
|
48
|
+
try:
|
|
49
|
+
smiles = get_smiles(mol)
|
|
50
|
+
mol = get_mol(smiles)
|
|
51
|
+
except Exception as e:
|
|
52
|
+
return None
|
|
53
|
+
return mol
|
|
54
|
+
|
|
55
|
+
def copy_atom(atom):
|
|
56
|
+
new_atom = Chem.Atom(atom.GetSymbol())
|
|
57
|
+
new_atom.SetFormalCharge(atom.GetFormalCharge())
|
|
58
|
+
new_atom.SetAtomMapNum(atom.GetAtomMapNum())
|
|
59
|
+
return new_atom
|
|
60
|
+
|
|
61
|
+
def copy_edit_mol(mol):
|
|
62
|
+
new_mol = Chem.RWMol(Chem.MolFromSmiles(''))
|
|
63
|
+
for atom in mol.GetAtoms():
|
|
64
|
+
new_atom = copy_atom(atom)
|
|
65
|
+
new_mol.AddAtom(new_atom)
|
|
66
|
+
for bond in mol.GetBonds():
|
|
67
|
+
a1 = bond.GetBeginAtom().GetIdx()
|
|
68
|
+
a2 = bond.GetEndAtom().GetIdx()
|
|
69
|
+
bt = bond.GetBondType()
|
|
70
|
+
new_mol.AddBond(a1, a2, bt)
|
|
71
|
+
return new_mol
|
|
72
|
+
|
|
73
|
+
def get_clique_mol(mol, atoms):
|
|
74
|
+
# smiles = Chem.MolFragmentToSmiles(mol, atoms, kekuleSmiles=True)
|
|
75
|
+
# new_mol = Chem.MolFromSmiles(smiles, sanitize=False)
|
|
76
|
+
# new_mol = copy_edit_mol(new_mol).GetMol()
|
|
77
|
+
# new_mol = sanitize(new_mol) #We assume this is not None
|
|
78
|
+
# return new_mol
|
|
79
|
+
try:
|
|
80
|
+
smiles = Chem.MolFragmentToSmiles(mol, atoms, kekuleSmiles=True)
|
|
81
|
+
except Exception as e:
|
|
82
|
+
numbers = [int(num) for num in re.findall(r'\b\d+\b', str(e))]
|
|
83
|
+
# print('e', e)
|
|
84
|
+
# print('numbers', numbers, 'atoms', atoms)
|
|
85
|
+
# print('moltosmie', Chem.MolToSmiles(mol))
|
|
86
|
+
# print('Chem.MolFragmentToSmiles(mol, atoms, kekuleSmiles=False)', Chem.MolFragmentToSmiles(mol, atoms, kekuleSmiles=False))
|
|
87
|
+
for num in numbers:
|
|
88
|
+
mol.GetAtomWithIdx(num).SetNumExplicitHs(1)
|
|
89
|
+
smiles = Chem.MolFragmentToSmiles(mol, atoms, kekuleSmiles=True)
|
|
90
|
+
smiles = Chem.MolToSmiles(Chem.MolFromSmiles(smiles), kekuleSmiles=True)
|
|
91
|
+
new_mol = Chem.MolFromSmiles(smiles, sanitize=False)
|
|
92
|
+
new_mol = copy_edit_mol(new_mol).GetMol()
|
|
93
|
+
new_mol = sanitize(new_mol) #We assume this is not None
|
|
94
|
+
return new_mol
|
|
95
|
+
|
|
96
|
+
def tree_decomp(mol):
|
|
97
|
+
n_atoms = mol.GetNumAtoms()
|
|
98
|
+
if n_atoms == 1: #special case
|
|
99
|
+
return [[0]], []
|
|
100
|
+
|
|
101
|
+
cliques = []
|
|
102
|
+
for bond in mol.GetBonds():
|
|
103
|
+
a1 = bond.GetBeginAtom().GetIdx()
|
|
104
|
+
a2 = bond.GetEndAtom().GetIdx()
|
|
105
|
+
if not bond.IsInRing():
|
|
106
|
+
cliques.append([a1,a2])
|
|
107
|
+
|
|
108
|
+
ssr = [list(x) for x in Chem.GetSymmSSSR(mol)]
|
|
109
|
+
cliques.extend(ssr)
|
|
110
|
+
|
|
111
|
+
nei_list = [[] for i in range(n_atoms)]
|
|
112
|
+
for i in range(len(cliques)):
|
|
113
|
+
for atom in cliques[i]:
|
|
114
|
+
nei_list[atom].append(i)
|
|
115
|
+
|
|
116
|
+
#Merge Rings with intersection > 2 atoms
|
|
117
|
+
for i in range(len(cliques)):
|
|
118
|
+
if len(cliques[i]) <= 2: continue
|
|
119
|
+
for atom in cliques[i]:
|
|
120
|
+
for j in nei_list[atom]:
|
|
121
|
+
if i >= j or len(cliques[j]) <= 2: continue
|
|
122
|
+
inter = set(cliques[i]) & set(cliques[j])
|
|
123
|
+
if len(inter) > 2:
|
|
124
|
+
cliques[i].extend(cliques[j])
|
|
125
|
+
cliques[i] = list(set(cliques[i]))
|
|
126
|
+
cliques[j] = []
|
|
127
|
+
|
|
128
|
+
cliques = [c for c in cliques if len(c) > 0]
|
|
129
|
+
nei_list = [[] for i in range(n_atoms)]
|
|
130
|
+
for i in range(len(cliques)):
|
|
131
|
+
for atom in cliques[i]:
|
|
132
|
+
nei_list[atom].append(i)
|
|
133
|
+
|
|
134
|
+
#Build edges and add singleton cliques
|
|
135
|
+
edges = defaultdict(int)
|
|
136
|
+
for atom in range(n_atoms):
|
|
137
|
+
if len(nei_list[atom]) <= 1:
|
|
138
|
+
continue
|
|
139
|
+
cnei = nei_list[atom]
|
|
140
|
+
bonds = [c for c in cnei if len(cliques[c]) == 2]
|
|
141
|
+
rings = [c for c in cnei if len(cliques[c]) > 4]
|
|
142
|
+
if len(bonds) > 2 or (len(bonds) == 2 and len(cnei) > 2): #In general, if len(cnei) >= 3, a singleton should be added, but 1 bond + 2 ring is currently not dealt with.
|
|
143
|
+
cliques.append([atom])
|
|
144
|
+
c2 = len(cliques) - 1
|
|
145
|
+
for c1 in cnei:
|
|
146
|
+
edges[(c1,c2)] = 1
|
|
147
|
+
elif len(rings) > 2: #Multiple (n>2) complex rings
|
|
148
|
+
cliques.append([atom])
|
|
149
|
+
c2 = len(cliques) - 1
|
|
150
|
+
for c1 in cnei:
|
|
151
|
+
edges[(c1,c2)] = MST_MAX_WEIGHT - 1
|
|
152
|
+
else:
|
|
153
|
+
for i in range(len(cnei)):
|
|
154
|
+
for j in range(i + 1, len(cnei)):
|
|
155
|
+
c1,c2 = cnei[i],cnei[j]
|
|
156
|
+
inter = set(cliques[c1]) & set(cliques[c2])
|
|
157
|
+
if edges[(c1,c2)] < len(inter):
|
|
158
|
+
edges[(c1,c2)] = len(inter) #cnei[i] < cnei[j] by construction
|
|
159
|
+
|
|
160
|
+
edges = [u + (MST_MAX_WEIGHT-v,) for u,v in edges.items()]
|
|
161
|
+
if len(edges) == 0:
|
|
162
|
+
return cliques, edges
|
|
163
|
+
|
|
164
|
+
#Compute Maximum Spanning Tree
|
|
165
|
+
row,col,data = zip(*edges)
|
|
166
|
+
n_clique = len(cliques)
|
|
167
|
+
clique_graph = csr_matrix( (data,(row,col)), shape=(n_clique,n_clique) )
|
|
168
|
+
junc_tree = minimum_spanning_tree(clique_graph)
|
|
169
|
+
row,col = junc_tree.nonzero()
|
|
170
|
+
edges = [(row[i],col[i]) for i in range(len(row))]
|
|
171
|
+
return (cliques, edges)
|
|
172
|
+
|
|
173
|
+
def atom_equal(a1, a2):
|
|
174
|
+
return a1.GetSymbol() == a2.GetSymbol() and a1.GetFormalCharge() == a2.GetFormalCharge()
|
|
175
|
+
|
|
176
|
+
#Bond type not considered because all aromatic (so SINGLE matches DOUBLE)
|
|
177
|
+
def ring_bond_equal(b1, b2, reverse=False):
|
|
178
|
+
b1 = (b1.GetBeginAtom(), b1.GetEndAtom())
|
|
179
|
+
if reverse:
|
|
180
|
+
b2 = (b2.GetEndAtom(), b2.GetBeginAtom())
|
|
181
|
+
else:
|
|
182
|
+
b2 = (b2.GetBeginAtom(), b2.GetEndAtom())
|
|
183
|
+
return atom_equal(b1[0], b2[0]) and atom_equal(b1[1], b2[1])
|
|
184
|
+
|
|
185
|
+
def attach_mols(ctr_mol, neighbors, prev_nodes, nei_amap):
|
|
186
|
+
prev_nids = [node.nid for node in prev_nodes]
|
|
187
|
+
for nei_node in prev_nodes + neighbors:
|
|
188
|
+
nei_id,nei_mol = nei_node.nid,nei_node.mol
|
|
189
|
+
amap = nei_amap[nei_id]
|
|
190
|
+
for atom in nei_mol.GetAtoms():
|
|
191
|
+
if atom.GetIdx() not in amap:
|
|
192
|
+
new_atom = copy_atom(atom)
|
|
193
|
+
amap[atom.GetIdx()] = ctr_mol.AddAtom(new_atom)
|
|
194
|
+
|
|
195
|
+
if nei_mol.GetNumBonds() == 0:
|
|
196
|
+
nei_atom = nei_mol.GetAtomWithIdx(0)
|
|
197
|
+
ctr_atom = ctr_mol.GetAtomWithIdx(amap[0])
|
|
198
|
+
ctr_atom.SetAtomMapNum(nei_atom.GetAtomMapNum())
|
|
199
|
+
else:
|
|
200
|
+
for bond in nei_mol.GetBonds():
|
|
201
|
+
a1 = amap[bond.GetBeginAtom().GetIdx()]
|
|
202
|
+
a2 = amap[bond.GetEndAtom().GetIdx()]
|
|
203
|
+
if ctr_mol.GetBondBetweenAtoms(a1, a2) is None:
|
|
204
|
+
ctr_mol.AddBond(a1, a2, bond.GetBondType())
|
|
205
|
+
elif nei_id in prev_nids: #father node overrides
|
|
206
|
+
ctr_mol.RemoveBond(a1, a2)
|
|
207
|
+
ctr_mol.AddBond(a1, a2, bond.GetBondType())
|
|
208
|
+
return ctr_mol
|
|
209
|
+
|
|
210
|
+
def local_attach(ctr_mol, neighbors, prev_nodes, amap_list):
|
|
211
|
+
ctr_mol = copy_edit_mol(ctr_mol)
|
|
212
|
+
nei_amap = {nei.nid:{} for nei in prev_nodes + neighbors}
|
|
213
|
+
|
|
214
|
+
for nei_id,ctr_atom,nei_atom in amap_list:
|
|
215
|
+
nei_amap[nei_id][nei_atom] = ctr_atom
|
|
216
|
+
|
|
217
|
+
ctr_mol = attach_mols(ctr_mol, neighbors, prev_nodes, nei_amap)
|
|
218
|
+
return ctr_mol.GetMol()
|
|
219
|
+
|
|
220
|
+
#This version records idx mapping between ctr_mol and nei_mol
|
|
221
|
+
def enum_attach(ctr_mol, nei_node, amap, singletons):
|
|
222
|
+
nei_mol,nei_idx = nei_node.mol,nei_node.nid
|
|
223
|
+
att_confs = []
|
|
224
|
+
black_list = [atom_idx for nei_id,atom_idx,_ in amap if nei_id in singletons]
|
|
225
|
+
ctr_atoms = [atom for atom in ctr_mol.GetAtoms() if atom.GetIdx() not in black_list]
|
|
226
|
+
ctr_bonds = [bond for bond in ctr_mol.GetBonds()]
|
|
227
|
+
|
|
228
|
+
if nei_mol.GetNumBonds() == 0: #neighbor singleton
|
|
229
|
+
nei_atom = nei_mol.GetAtomWithIdx(0)
|
|
230
|
+
used_list = [atom_idx for _,atom_idx,_ in amap]
|
|
231
|
+
for atom in ctr_atoms:
|
|
232
|
+
if atom_equal(atom, nei_atom) and atom.GetIdx() not in used_list:
|
|
233
|
+
new_amap = amap + [(nei_idx, atom.GetIdx(), 0)]
|
|
234
|
+
att_confs.append( new_amap )
|
|
235
|
+
|
|
236
|
+
elif nei_mol.GetNumBonds() == 1: #neighbor is a bond
|
|
237
|
+
bond = nei_mol.GetBondWithIdx(0)
|
|
238
|
+
bond_val = int(bond.GetBondTypeAsDouble())
|
|
239
|
+
b1,b2 = bond.GetBeginAtom(), bond.GetEndAtom()
|
|
240
|
+
|
|
241
|
+
for atom in ctr_atoms:
|
|
242
|
+
#Optimize if atom is carbon (other atoms may change valence)
|
|
243
|
+
if atom.GetAtomicNum() == 6 and atom.GetTotalNumHs() < bond_val:
|
|
244
|
+
continue
|
|
245
|
+
if atom_equal(atom, b1):
|
|
246
|
+
new_amap = amap + [(nei_idx, atom.GetIdx(), b1.GetIdx())]
|
|
247
|
+
att_confs.append( new_amap )
|
|
248
|
+
elif atom_equal(atom, b2):
|
|
249
|
+
new_amap = amap + [(nei_idx, atom.GetIdx(), b2.GetIdx())]
|
|
250
|
+
att_confs.append( new_amap )
|
|
251
|
+
else:
|
|
252
|
+
#intersection is an atom
|
|
253
|
+
for a1 in ctr_atoms:
|
|
254
|
+
for a2 in nei_mol.GetAtoms():
|
|
255
|
+
if atom_equal(a1, a2):
|
|
256
|
+
#Optimize if atom is carbon (other atoms may change valence)
|
|
257
|
+
if a1.GetAtomicNum() == 6 and a1.GetTotalNumHs() + a2.GetTotalNumHs() < 4:
|
|
258
|
+
continue
|
|
259
|
+
new_amap = amap + [(nei_idx, a1.GetIdx(), a2.GetIdx())]
|
|
260
|
+
att_confs.append( new_amap )
|
|
261
|
+
|
|
262
|
+
#intersection is an bond
|
|
263
|
+
if ctr_mol.GetNumBonds() > 1:
|
|
264
|
+
for b1 in ctr_bonds:
|
|
265
|
+
for b2 in nei_mol.GetBonds():
|
|
266
|
+
if ring_bond_equal(b1, b2):
|
|
267
|
+
new_amap = amap + [(nei_idx, b1.GetBeginAtom().GetIdx(), b2.GetBeginAtom().GetIdx()), (nei_idx, b1.GetEndAtom().GetIdx(), b2.GetEndAtom().GetIdx())]
|
|
268
|
+
att_confs.append( new_amap )
|
|
269
|
+
|
|
270
|
+
if ring_bond_equal(b1, b2, reverse=True):
|
|
271
|
+
new_amap = amap + [(nei_idx, b1.GetBeginAtom().GetIdx(), b2.GetEndAtom().GetIdx()), (nei_idx, b1.GetEndAtom().GetIdx(), b2.GetBeginAtom().GetIdx())]
|
|
272
|
+
att_confs.append( new_amap )
|
|
273
|
+
return att_confs
|
|
274
|
+
|
|
275
|
+
#Try rings first: Speed-Up
|
|
276
|
+
def enum_assemble(node, neighbors, prev_nodes=[], prev_amap=[]):
|
|
277
|
+
all_attach_confs = []
|
|
278
|
+
singletons = [nei_node.nid for nei_node in neighbors + prev_nodes if nei_node.mol.GetNumAtoms() == 1]
|
|
279
|
+
|
|
280
|
+
def search(cur_amap, depth):
|
|
281
|
+
if len(all_attach_confs) > MAX_NCAND:
|
|
282
|
+
return
|
|
283
|
+
if depth == len(neighbors):
|
|
284
|
+
all_attach_confs.append(cur_amap)
|
|
285
|
+
return
|
|
286
|
+
|
|
287
|
+
nei_node = neighbors[depth]
|
|
288
|
+
cand_amap = enum_attach(node.mol, nei_node, cur_amap, singletons)
|
|
289
|
+
cand_smiles = set()
|
|
290
|
+
candidates = []
|
|
291
|
+
for amap in cand_amap:
|
|
292
|
+
cand_mol = local_attach(node.mol, neighbors[:depth+1], prev_nodes, amap)
|
|
293
|
+
cand_mol = sanitize(cand_mol)
|
|
294
|
+
if cand_mol is None:
|
|
295
|
+
continue
|
|
296
|
+
smiles = get_smiles(cand_mol)
|
|
297
|
+
if smiles in cand_smiles:
|
|
298
|
+
continue
|
|
299
|
+
cand_smiles.add(smiles)
|
|
300
|
+
candidates.append(amap)
|
|
301
|
+
|
|
302
|
+
if len(candidates) == 0:
|
|
303
|
+
return
|
|
304
|
+
|
|
305
|
+
for new_amap in candidates:
|
|
306
|
+
search(new_amap, depth + 1)
|
|
307
|
+
|
|
308
|
+
search(prev_amap, 0)
|
|
309
|
+
cand_smiles = set()
|
|
310
|
+
candidates = []
|
|
311
|
+
aroma_score = []
|
|
312
|
+
for amap in all_attach_confs:
|
|
313
|
+
cand_mol = local_attach(node.mol, neighbors, prev_nodes, amap)
|
|
314
|
+
cand_mol = Chem.MolFromSmiles(Chem.MolToSmiles(cand_mol))
|
|
315
|
+
smiles = Chem.MolToSmiles(cand_mol)
|
|
316
|
+
if smiles in cand_smiles or check_singleton(cand_mol, node, neighbors) == False:
|
|
317
|
+
continue
|
|
318
|
+
cand_smiles.add(smiles)
|
|
319
|
+
candidates.append( (smiles,amap) )
|
|
320
|
+
aroma_score.append( check_aroma(cand_mol, node, neighbors) )
|
|
321
|
+
|
|
322
|
+
return candidates, aroma_score
|
|
323
|
+
|
|
324
|
+
def check_singleton(cand_mol, ctr_node, nei_nodes):
|
|
325
|
+
rings = [node for node in nei_nodes + [ctr_node] if node.mol.GetNumAtoms() > 2]
|
|
326
|
+
singletons = [node for node in nei_nodes + [ctr_node] if node.mol.GetNumAtoms() == 1]
|
|
327
|
+
if len(singletons) > 0 or len(rings) == 0: return True
|
|
328
|
+
|
|
329
|
+
n_leaf2_atoms = 0
|
|
330
|
+
for atom in cand_mol.GetAtoms():
|
|
331
|
+
nei_leaf_atoms = [a for a in atom.GetNeighbors() if not a.IsInRing()] #a.GetDegree() == 1]
|
|
332
|
+
if len(nei_leaf_atoms) > 1:
|
|
333
|
+
n_leaf2_atoms += 1
|
|
334
|
+
|
|
335
|
+
return n_leaf2_atoms == 0
|
|
336
|
+
|
|
337
|
+
def check_aroma(cand_mol, ctr_node, nei_nodes):
|
|
338
|
+
rings = [node for node in nei_nodes + [ctr_node] if node.mol.GetNumAtoms() >= 3]
|
|
339
|
+
if len(rings) < 2: return 0 #Only multi-ring system needs to be checked
|
|
340
|
+
|
|
341
|
+
get_nid = lambda x: 0 if x.is_leaf else x.nid
|
|
342
|
+
benzynes = [get_nid(node) for node in nei_nodes + [ctr_node] if node.smiles in Vocab.benzynes]
|
|
343
|
+
penzynes = [get_nid(node) for node in nei_nodes + [ctr_node] if node.smiles in Vocab.penzynes]
|
|
344
|
+
if len(benzynes) + len(penzynes) == 0:
|
|
345
|
+
return 0 #No specific aromatic rings
|
|
346
|
+
|
|
347
|
+
n_aroma_atoms = 0
|
|
348
|
+
for atom in cand_mol.GetAtoms():
|
|
349
|
+
if atom.GetAtomMapNum() in benzynes+penzynes and atom.GetIsAromatic():
|
|
350
|
+
n_aroma_atoms += 1
|
|
351
|
+
|
|
352
|
+
if n_aroma_atoms >= len(benzynes) * 4 + len(penzynes) * 3:
|
|
353
|
+
return 1000
|
|
354
|
+
else:
|
|
355
|
+
return -0.001
|
|
356
|
+
|
|
357
|
+
#Only used for debugging purpose
|
|
358
|
+
def dfs_assemble(cur_mol, global_amap, fa_amap, cur_node, fa_node):
|
|
359
|
+
fa_nid = fa_node.nid if fa_node is not None else -1
|
|
360
|
+
prev_nodes = [fa_node] if fa_node is not None else []
|
|
361
|
+
|
|
362
|
+
children = [nei for nei in cur_node.neighbors if nei.nid != fa_nid]
|
|
363
|
+
neighbors = [nei for nei in children if nei.mol.GetNumAtoms() > 1]
|
|
364
|
+
neighbors = sorted(neighbors, key=lambda x:x.mol.GetNumAtoms(), reverse=True)
|
|
365
|
+
singletons = [nei for nei in children if nei.mol.GetNumAtoms() == 1]
|
|
366
|
+
neighbors = singletons + neighbors
|
|
367
|
+
|
|
368
|
+
cur_amap = [(fa_nid,a2,a1) for nid,a1,a2 in fa_amap if nid == cur_node.nid]
|
|
369
|
+
cands = enum_assemble(cur_node, neighbors, prev_nodes, cur_amap)
|
|
370
|
+
|
|
371
|
+
cand_smiles,cand_amap = zip(*cands)
|
|
372
|
+
label_idx = cand_smiles.index(cur_node.label)
|
|
373
|
+
label_amap = cand_amap[label_idx]
|
|
374
|
+
|
|
375
|
+
for nei_id,ctr_atom,nei_atom in label_amap:
|
|
376
|
+
if nei_id == fa_nid:
|
|
377
|
+
continue
|
|
378
|
+
global_amap[nei_id][nei_atom] = global_amap[cur_node.nid][ctr_atom]
|
|
379
|
+
|
|
380
|
+
cur_mol = attach_mols(cur_mol, children, [], global_amap) #father is already attached
|
|
381
|
+
for nei_node in children:
|
|
382
|
+
if not nei_node.is_leaf:
|
|
383
|
+
dfs_assemble(cur_mol, global_amap, label_amap, nei_node, cur_node)
|
|
384
|
+
|
|
385
|
+
if __name__ == "__main__":
|
|
386
|
+
import sys
|
|
387
|
+
from mol_tree import MolTree
|
|
388
|
+
lg = rdkit.RDLogger.logger()
|
|
389
|
+
lg.setLevel(rdkit.RDLogger.CRITICAL)
|
|
390
|
+
|
|
391
|
+
smiles = ["O=C1[C@@H]2C=C[C@@H](C=CC2)C1(c1ccccc1)c1ccccc1","O=C([O-])CC[C@@]12CCCC[C@]1(O)OC(=O)CC2", "ON=C1C[C@H]2CC3(C[C@@H](C1)c1ccccc12)OCCO3", "C[C@H]1CC(=O)[C@H]2[C@@]3(O)C(=O)c4cccc(O)c4[C@@H]4O[C@@]43[C@@H](O)C[C@]2(O)C1", 'Cc1cc(NC(=O)CSc2nnc3c4ccccc4n(C)c3n2)ccc1Br', 'CC(C)(C)c1ccc(C(=O)N[C@H]2CCN3CCCc4cccc2c43)cc1', "O=c1c2ccc3c(=O)n(-c4nccs4)c(=O)c4ccc(c(=O)n1-c1nccs1)c2c34", "O=C(N1CCc2c(F)ccc(F)c2C1)C1(O)Cc2ccccc2C1"]
|
|
392
|
+
|
|
393
|
+
def tree_test():
|
|
394
|
+
for s in sys.stdin:
|
|
395
|
+
s = s.split()[0]
|
|
396
|
+
tree = MolTree(s)
|
|
397
|
+
print('-------------------------------------------')
|
|
398
|
+
print(s)
|
|
399
|
+
for node in tree.nodes:
|
|
400
|
+
print(node.smiles, [x.smiles for x in node.neighbors])
|
|
401
|
+
|
|
402
|
+
def decode_test():
|
|
403
|
+
wrong = 0
|
|
404
|
+
for tot,s in enumerate(sys.stdin):
|
|
405
|
+
s = s.split()[0]
|
|
406
|
+
tree = MolTree(s)
|
|
407
|
+
tree.recover()
|
|
408
|
+
|
|
409
|
+
cur_mol = copy_edit_mol(tree.nodes[0].mol)
|
|
410
|
+
global_amap = [{}] + [{} for node in tree.nodes]
|
|
411
|
+
global_amap[1] = {atom.GetIdx():atom.GetIdx() for atom in cur_mol.GetAtoms()}
|
|
412
|
+
|
|
413
|
+
dfs_assemble(cur_mol, global_amap, [], tree.nodes[0], None)
|
|
414
|
+
|
|
415
|
+
cur_mol = cur_mol.GetMol()
|
|
416
|
+
cur_mol = Chem.MolFromSmiles(Chem.MolToSmiles(cur_mol))
|
|
417
|
+
set_atommap(cur_mol)
|
|
418
|
+
dec_smiles = Chem.MolToSmiles(cur_mol)
|
|
419
|
+
|
|
420
|
+
gold_smiles = Chem.MolToSmiles(Chem.MolFromSmiles(s))
|
|
421
|
+
if gold_smiles != dec_smiles:
|
|
422
|
+
print(gold_smiles, dec_smiles)
|
|
423
|
+
wrong += 1
|
|
424
|
+
print(wrong, tot + 1)
|
|
425
|
+
|
|
426
|
+
def enum_test():
|
|
427
|
+
for s in sys.stdin:
|
|
428
|
+
s = s.split()[0]
|
|
429
|
+
tree = MolTree(s)
|
|
430
|
+
tree.recover()
|
|
431
|
+
tree.assemble()
|
|
432
|
+
for node in tree.nodes:
|
|
433
|
+
if node.label not in node.cands:
|
|
434
|
+
print(tree.smiles)
|
|
435
|
+
print(node.smiles, [x.smiles for x in node.neighbors])
|
|
436
|
+
print(node.label, len(node.cands))
|
|
437
|
+
|
|
438
|
+
def count():
|
|
439
|
+
cnt,n = 0,0
|
|
440
|
+
for s in sys.stdin:
|
|
441
|
+
s = s.split()[0]
|
|
442
|
+
tree = MolTree(s)
|
|
443
|
+
tree.recover()
|
|
444
|
+
tree.assemble()
|
|
445
|
+
for node in tree.nodes:
|
|
446
|
+
cnt += len(node.cands)
|
|
447
|
+
n += len(tree.nodes)
|
|
448
|
+
#print(cnt * 1.0 / n)
|
|
449
|
+
|
|
450
|
+
count()
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from torch.utils.data import Dataset, DataLoader
|
|
3
|
+
from .mol_tree import MolTree
|
|
4
|
+
import numpy as np
|
|
5
|
+
from .jtnn_enc import JTNNEncoder
|
|
6
|
+
from .mpn import MPN
|
|
7
|
+
from .jtmpn import JTMPN
|
|
8
|
+
import pickle
|
|
9
|
+
import os, random
|
|
10
|
+
|
|
11
|
+
# class PairTreeFolder(object):
|
|
12
|
+
# def __init__(self, data_folder, vocab, batch_size, num_workers=4, shuffle=True, y_assm=True, replicate=None):
|
|
13
|
+
# self.data_folder = data_folder
|
|
14
|
+
# self.data_files = [fn for fn in os.listdir(data_folder)]
|
|
15
|
+
# self.batch_size = batch_size
|
|
16
|
+
# self.vocab = vocab
|
|
17
|
+
# self.num_workers = num_workers
|
|
18
|
+
# self.y_assm = y_assm
|
|
19
|
+
# self.shuffle = shuffle
|
|
20
|
+
|
|
21
|
+
# if replicate is not None: #expand is int
|
|
22
|
+
# self.data_files = self.data_files * replicate
|
|
23
|
+
|
|
24
|
+
# def __iter__(self):
|
|
25
|
+
# for fn in self.data_files:
|
|
26
|
+
# fn = os.path.join(self.data_folder, fn)
|
|
27
|
+
# with open(fn) as f:
|
|
28
|
+
# data = pickle.load(f)
|
|
29
|
+
|
|
30
|
+
# if self.shuffle:
|
|
31
|
+
# random.shuffle(data) #shuffle data before batch
|
|
32
|
+
|
|
33
|
+
# batches = [data[i : i + self.batch_size] for i in range(0, len(data), self.batch_size)]
|
|
34
|
+
# if len(batches[-1]) < self.batch_size:
|
|
35
|
+
# batches.pop()
|
|
36
|
+
|
|
37
|
+
# dataset = PairTreeDataset(batches, self.vocab, self.y_assm)
|
|
38
|
+
# dataloader = DataLoader(dataset, batch_size=1, shuffle=False, num_workers=self.num_workers, collate_fn=lambda x:x[0])
|
|
39
|
+
|
|
40
|
+
# for b in dataloader:
|
|
41
|
+
# yield b
|
|
42
|
+
|
|
43
|
+
# del data, batches, dataset, dataloader
|
|
44
|
+
|
|
45
|
+
class MolTreeFolder(object):
|
|
46
|
+
def __init__(self, data, vocab, batch_size, num_workers=0, shuffle=True, assm=True):
|
|
47
|
+
self.data = data
|
|
48
|
+
self.batch_size = batch_size
|
|
49
|
+
self.vocab = vocab
|
|
50
|
+
self.num_workers = num_workers
|
|
51
|
+
self.shuffle = shuffle
|
|
52
|
+
self.assm = assm
|
|
53
|
+
|
|
54
|
+
def __iter__(self):
|
|
55
|
+
data = self.data
|
|
56
|
+
if self.shuffle:
|
|
57
|
+
random.shuffle(data) # shuffle data before batch
|
|
58
|
+
|
|
59
|
+
batches = [data[i : i + self.batch_size] for i in range(0, len(data), self.batch_size)]
|
|
60
|
+
if len(batches[-1]) < self.batch_size:
|
|
61
|
+
batches.pop()
|
|
62
|
+
|
|
63
|
+
dataset = MolTreeDataset(batches, self.vocab, self.assm)
|
|
64
|
+
dataloader = DataLoader(dataset, batch_size=1, shuffle=False, num_workers=self.num_workers, collate_fn=lambda x:x[0])
|
|
65
|
+
|
|
66
|
+
for b in dataloader:
|
|
67
|
+
yield b
|
|
68
|
+
|
|
69
|
+
del data, batches, dataset, dataloader
|
|
70
|
+
|
|
71
|
+
class PairTreeDataset(Dataset):
|
|
72
|
+
|
|
73
|
+
def __init__(self, data, vocab, y_assm):
|
|
74
|
+
self.data = data
|
|
75
|
+
self.vocab = vocab
|
|
76
|
+
self.y_assm = y_assm
|
|
77
|
+
|
|
78
|
+
def __len__(self):
|
|
79
|
+
return len(self.data)
|
|
80
|
+
|
|
81
|
+
def __getitem__(self, idx):
|
|
82
|
+
batch0, batch1 = zip(*self.data[idx])
|
|
83
|
+
return tensorize(batch0, self.vocab, assm=False), tensorize(batch1, self.vocab, assm=self.y_assm)
|
|
84
|
+
|
|
85
|
+
class MolTreeDataset(Dataset):
|
|
86
|
+
def __init__(self, data, vocab, assm=True):
|
|
87
|
+
self.data = data
|
|
88
|
+
self.vocab = vocab
|
|
89
|
+
self.assm = assm
|
|
90
|
+
|
|
91
|
+
def __len__(self):
|
|
92
|
+
return len(self.data)
|
|
93
|
+
|
|
94
|
+
def __getitem__(self, idx):
|
|
95
|
+
return tensorize(self.data[idx], self.vocab, assm=self.assm)
|
|
96
|
+
|
|
97
|
+
def tensorize(tree_batch, vocab, assm=True):
|
|
98
|
+
set_batch_nodeID(tree_batch, vocab)
|
|
99
|
+
smiles_batch = [tree.smiles for tree in tree_batch]
|
|
100
|
+
jtenc_holder,mess_dict = JTNNEncoder.tensorize(tree_batch)
|
|
101
|
+
jtenc_holder = jtenc_holder
|
|
102
|
+
mpn_holder = MPN.tensorize(smiles_batch)
|
|
103
|
+
|
|
104
|
+
if assm is False:
|
|
105
|
+
return tree_batch, jtenc_holder, mpn_holder
|
|
106
|
+
|
|
107
|
+
cands = []
|
|
108
|
+
batch_idx = []
|
|
109
|
+
for i,mol_tree in enumerate(tree_batch):
|
|
110
|
+
for node in mol_tree.nodes:
|
|
111
|
+
#Leaf node's attachment is determined by neighboring node's attachment
|
|
112
|
+
if node.is_leaf or len(node.cands) == 1: continue
|
|
113
|
+
cands.extend( [(cand, mol_tree.nodes, node) for cand in node.cands] )
|
|
114
|
+
batch_idx.extend([i] * len(node.cands))
|
|
115
|
+
|
|
116
|
+
jtmpn_holder = JTMPN.tensorize(cands, mess_dict)
|
|
117
|
+
batch_idx = torch.LongTensor(batch_idx)
|
|
118
|
+
|
|
119
|
+
return tree_batch, jtenc_holder, mpn_holder, (jtmpn_holder,batch_idx)
|
|
120
|
+
|
|
121
|
+
def set_batch_nodeID(mol_batch, vocab):
|
|
122
|
+
tot = 0
|
|
123
|
+
for mol_tree in mol_batch:
|
|
124
|
+
for node in mol_tree.nodes:
|
|
125
|
+
node.idx = tot
|
|
126
|
+
node.wid = vocab.get_index(node.smiles)
|
|
127
|
+
tot += 1
|