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,178 @@
|
|
|
1
|
+
# allowable multiple choice node and edge features
|
|
2
|
+
allowable_features = {
|
|
3
|
+
# atom types: 1-118, 119 is masked atom, 120 is misc (e.g. * for polymers)
|
|
4
|
+
# index: 0-117, 118, 119
|
|
5
|
+
'possible_atomic_num_list' : list(range(1, 120)) + ['misc'],
|
|
6
|
+
'possible_chirality_list' : [
|
|
7
|
+
'CHI_UNSPECIFIED',
|
|
8
|
+
'CHI_TETRAHEDRAL_CW',
|
|
9
|
+
'CHI_TETRAHEDRAL_CCW',
|
|
10
|
+
'CHI_OTHER',
|
|
11
|
+
'misc'
|
|
12
|
+
],
|
|
13
|
+
'possible_degree_list' : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 'misc'],
|
|
14
|
+
'possible_formal_charge_list' : [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 'misc'],
|
|
15
|
+
'possible_numH_list' : [0, 1, 2, 3, 4, 5, 6, 7, 8, 'misc'],
|
|
16
|
+
'possible_number_radical_e_list': [0, 1, 2, 3, 4, 'misc'],
|
|
17
|
+
'possible_hybridization_list' : [
|
|
18
|
+
'SP', 'SP2', 'SP3', 'SP3D', 'SP3D2', 'misc'
|
|
19
|
+
],
|
|
20
|
+
'possible_is_aromatic_list': [False, True],
|
|
21
|
+
'possible_is_in_ring_list': [False, True],
|
|
22
|
+
'possible_bond_type_list' : [
|
|
23
|
+
'SINGLE',
|
|
24
|
+
'DOUBLE',
|
|
25
|
+
'TRIPLE',
|
|
26
|
+
'AROMATIC',
|
|
27
|
+
'misc'
|
|
28
|
+
],
|
|
29
|
+
'possible_bond_stereo_list': [
|
|
30
|
+
'STEREONONE',
|
|
31
|
+
'STEREOZ',
|
|
32
|
+
'STEREOE',
|
|
33
|
+
'STEREOCIS',
|
|
34
|
+
'STEREOTRANS',
|
|
35
|
+
'STEREOANY',
|
|
36
|
+
],
|
|
37
|
+
'possible_is_conjugated_list': [False, True],
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
def safe_index(l, e):
|
|
41
|
+
"""
|
|
42
|
+
Return index of element e in list l. If e is not present, return the last index
|
|
43
|
+
"""
|
|
44
|
+
try:
|
|
45
|
+
return l.index(e)
|
|
46
|
+
except:
|
|
47
|
+
return len(l) - 1
|
|
48
|
+
# # miscellaneous case
|
|
49
|
+
# i = safe_index(allowable_features['possible_atomic_num_list'], 'asdf')
|
|
50
|
+
# assert allowable_features['possible_atomic_num_list'][i] == 'misc'
|
|
51
|
+
# # normal case
|
|
52
|
+
# i = safe_index(allowable_features['possible_atomic_num_list'], 2)
|
|
53
|
+
# assert allowable_features['possible_atomic_num_list'][i] == 2
|
|
54
|
+
|
|
55
|
+
def atom_to_feature_vector(atom):
|
|
56
|
+
"""
|
|
57
|
+
Converts rdkit atom object to feature list of indices
|
|
58
|
+
:param mol: rdkit atom object
|
|
59
|
+
:return: list
|
|
60
|
+
"""
|
|
61
|
+
atom_feature = [
|
|
62
|
+
safe_index(allowable_features['possible_atomic_num_list'], atom.GetAtomicNum()),
|
|
63
|
+
safe_index(allowable_features['possible_chirality_list'], str(atom.GetChiralTag())),
|
|
64
|
+
safe_index(allowable_features['possible_degree_list'], atom.GetTotalDegree()),
|
|
65
|
+
safe_index(allowable_features['possible_formal_charge_list'], atom.GetFormalCharge()),
|
|
66
|
+
safe_index(allowable_features['possible_numH_list'], atom.GetTotalNumHs()),
|
|
67
|
+
safe_index(allowable_features['possible_number_radical_e_list'], atom.GetNumRadicalElectrons()),
|
|
68
|
+
safe_index(allowable_features['possible_hybridization_list'], str(atom.GetHybridization())),
|
|
69
|
+
allowable_features['possible_is_aromatic_list'].index(atom.GetIsAromatic()),
|
|
70
|
+
allowable_features['possible_is_in_ring_list'].index(atom.IsInRing()),
|
|
71
|
+
]
|
|
72
|
+
return atom_feature
|
|
73
|
+
# from rdkit import Chem
|
|
74
|
+
# mol = Chem.MolFromSmiles('Cl[C@H](/C=C/C)Br')
|
|
75
|
+
# atom = mol.GetAtomWithIdx(1) # chiral carbon
|
|
76
|
+
# atom_feature = atom_to_feature_vector(atom)
|
|
77
|
+
# assert atom_feature == [5, 2, 4, 5, 1, 0, 2, 0, 0]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def get_atom_feature_dims():
|
|
81
|
+
return list(map(len, [
|
|
82
|
+
allowable_features['possible_atomic_num_list'],
|
|
83
|
+
allowable_features['possible_chirality_list'],
|
|
84
|
+
allowable_features['possible_degree_list'],
|
|
85
|
+
allowable_features['possible_formal_charge_list'],
|
|
86
|
+
allowable_features['possible_numH_list'],
|
|
87
|
+
allowable_features['possible_number_radical_e_list'],
|
|
88
|
+
allowable_features['possible_hybridization_list'],
|
|
89
|
+
allowable_features['possible_is_aromatic_list'],
|
|
90
|
+
allowable_features['possible_is_in_ring_list']
|
|
91
|
+
]))
|
|
92
|
+
|
|
93
|
+
def bond_to_feature_vector(bond):
|
|
94
|
+
"""
|
|
95
|
+
Converts rdkit bond object to feature list of indices
|
|
96
|
+
:param mol: rdkit bond object
|
|
97
|
+
:return: list
|
|
98
|
+
"""
|
|
99
|
+
bond_feature = [
|
|
100
|
+
safe_index(allowable_features['possible_bond_type_list'], str(bond.GetBondType())),
|
|
101
|
+
allowable_features['possible_bond_stereo_list'].index(str(bond.GetStereo())),
|
|
102
|
+
allowable_features['possible_is_conjugated_list'].index(bond.GetIsConjugated()),
|
|
103
|
+
]
|
|
104
|
+
return bond_feature
|
|
105
|
+
# uses same molecule as atom_to_feature_vector test
|
|
106
|
+
# bond = mol.GetBondWithIdx(2) # double bond with stereochem
|
|
107
|
+
# bond_feature = bond_to_feature_vector(bond)
|
|
108
|
+
# assert bond_feature == [1, 2, 0]
|
|
109
|
+
|
|
110
|
+
def get_bond_feature_dims():
|
|
111
|
+
return list(map(len, [
|
|
112
|
+
allowable_features['possible_bond_type_list'],
|
|
113
|
+
allowable_features['possible_bond_stereo_list'],
|
|
114
|
+
allowable_features['possible_is_conjugated_list']
|
|
115
|
+
]))
|
|
116
|
+
|
|
117
|
+
def atom_feature_vector_to_dict(atom_feature):
|
|
118
|
+
[atomic_num_idx,
|
|
119
|
+
chirality_idx,
|
|
120
|
+
degree_idx,
|
|
121
|
+
formal_charge_idx,
|
|
122
|
+
num_h_idx,
|
|
123
|
+
number_radical_e_idx,
|
|
124
|
+
hybridization_idx,
|
|
125
|
+
is_aromatic_idx,
|
|
126
|
+
is_in_ring_idx] = atom_feature
|
|
127
|
+
|
|
128
|
+
feature_dict = {
|
|
129
|
+
'atomic_num': allowable_features['possible_atomic_num_list'][atomic_num_idx],
|
|
130
|
+
'chirality': allowable_features['possible_chirality_list'][chirality_idx],
|
|
131
|
+
'degree': allowable_features['possible_degree_list'][degree_idx],
|
|
132
|
+
'formal_charge': allowable_features['possible_formal_charge_list'][formal_charge_idx],
|
|
133
|
+
'num_h': allowable_features['possible_numH_list'][num_h_idx],
|
|
134
|
+
'num_rad_e': allowable_features['possible_number_radical_e_list'][number_radical_e_idx],
|
|
135
|
+
'hybridization': allowable_features['possible_hybridization_list'][hybridization_idx],
|
|
136
|
+
'is_aromatic': allowable_features['possible_is_aromatic_list'][is_aromatic_idx],
|
|
137
|
+
'is_in_ring': allowable_features['possible_is_in_ring_list'][is_in_ring_idx]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return feature_dict
|
|
141
|
+
# # uses same atom_feature as atom_to_feature_vector test
|
|
142
|
+
# atom_feature_dict = atom_feature_vector_to_dict(atom_feature)
|
|
143
|
+
# assert atom_feature_dict['atomic_num'] == 6
|
|
144
|
+
# assert atom_feature_dict['chirality'] == 'CHI_TETRAHEDRAL_CCW'
|
|
145
|
+
# assert atom_feature_dict['degree'] == 4
|
|
146
|
+
# assert atom_feature_dict['formal_charge'] == 0
|
|
147
|
+
# assert atom_feature_dict['num_h'] == 1
|
|
148
|
+
# assert atom_feature_dict['num_rad_e'] == 0
|
|
149
|
+
# assert atom_feature_dict['hybridization'] == 'SP3'
|
|
150
|
+
# assert atom_feature_dict['is_aromatic'] == False
|
|
151
|
+
# assert atom_feature_dict['is_in_ring'] == False
|
|
152
|
+
|
|
153
|
+
def bond_feature_vector_to_dict(bond_feature):
|
|
154
|
+
[bond_type_idx,
|
|
155
|
+
bond_stereo_idx,
|
|
156
|
+
is_conjugated_idx] = bond_feature
|
|
157
|
+
|
|
158
|
+
feature_dict = {
|
|
159
|
+
'bond_type': allowable_features['possible_bond_type_list'][bond_type_idx],
|
|
160
|
+
'bond_stereo': allowable_features['possible_bond_stereo_list'][bond_stereo_idx],
|
|
161
|
+
'is_conjugated': allowable_features['possible_is_conjugated_list'][is_conjugated_idx]
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return feature_dict
|
|
165
|
+
# # uses same bond as bond_to_feature_vector test
|
|
166
|
+
# bond_feature_dict = bond_feature_vector_to_dict(bond_feature)
|
|
167
|
+
# assert bond_feature_dict['bond_type'] == 'DOUBLE'
|
|
168
|
+
# assert bond_feature_dict['bond_stereo'] == 'STEREOE'
|
|
169
|
+
# assert bond_feature_dict['is_conjugated'] == False
|
|
170
|
+
|
|
171
|
+
from rdkit.Chem import AllChem
|
|
172
|
+
|
|
173
|
+
def getmorganfingerprint(mol):
|
|
174
|
+
return list(AllChem.GetMorganFingerprintAsBitVect(mol, 2, nBits=1024))
|
|
175
|
+
|
|
176
|
+
def getmaccsfingerprint(mol):
|
|
177
|
+
fp = AllChem.GetMACCSKeysFingerprint(mol)
|
|
178
|
+
return [int(b) for b in fp.ToBitString()]
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from rdkit import Chem
|
|
3
|
+
from rdkit.Chem import Crippen
|
|
4
|
+
from .features import atom_to_feature_vector, bond_to_feature_vector
|
|
5
|
+
from .features import getmaccsfingerprint, getmorganfingerprint
|
|
6
|
+
from ..generic.pseudo_tasks import PSEUDOTASK
|
|
7
|
+
|
|
8
|
+
def add_fingerprint_feature(mol, feature_type, get_fingerprint_fn):
|
|
9
|
+
if feature_type is None:
|
|
10
|
+
return None
|
|
11
|
+
fingerprint = get_fingerprint_fn(mol)
|
|
12
|
+
return np.expand_dims(np.array(fingerprint, dtype="int8"), axis=0)
|
|
13
|
+
|
|
14
|
+
def get_augmented_property(mol, properties):
|
|
15
|
+
if mol is None:
|
|
16
|
+
return None
|
|
17
|
+
|
|
18
|
+
supported_properties = set(PSEUDOTASK.keys())
|
|
19
|
+
unsupported = set(properties) - supported_properties
|
|
20
|
+
if unsupported:
|
|
21
|
+
raise ValueError(f"Unsupported properties: {unsupported}. Supported properties are: {supported_properties}")
|
|
22
|
+
|
|
23
|
+
augmented_property = []
|
|
24
|
+
if 'maccs' in properties:
|
|
25
|
+
maccs = getmaccsfingerprint(mol)
|
|
26
|
+
augmented_property.extend(maccs)
|
|
27
|
+
if 'morgan' in properties:
|
|
28
|
+
mgf = getmorganfingerprint(mol)
|
|
29
|
+
augmented_property.extend(mgf)
|
|
30
|
+
if 'logP' in properties:
|
|
31
|
+
logp = Crippen.MolLogP(mol)
|
|
32
|
+
augmented_property.append(logp)
|
|
33
|
+
return augmented_property
|
|
34
|
+
|
|
35
|
+
def graph_from_smiles(smiles_or_mol, properties, augmented_features=None, augmented_properties=None):
|
|
36
|
+
"""
|
|
37
|
+
Converts SMILES string or RDKit molecule to graph Data object
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
smiles_or_mol : Union[str, rdkit.Chem.rdchem.Mol]
|
|
42
|
+
SMILES string or RDKit molecule object
|
|
43
|
+
properties : Any
|
|
44
|
+
Properties to include in the graph
|
|
45
|
+
augmented_features : list
|
|
46
|
+
List of augmented features to include
|
|
47
|
+
augmented_properties : list, optional
|
|
48
|
+
List of augmented properties to include
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
dict
|
|
53
|
+
Graph object dictionary
|
|
54
|
+
"""
|
|
55
|
+
# try:
|
|
56
|
+
if isinstance(smiles_or_mol, str):
|
|
57
|
+
mol = Chem.MolFromSmiles(smiles_or_mol)
|
|
58
|
+
else:
|
|
59
|
+
mol = smiles_or_mol
|
|
60
|
+
|
|
61
|
+
# atoms
|
|
62
|
+
atom_features_list = []
|
|
63
|
+
for atom in mol.GetAtoms():
|
|
64
|
+
# print(atom.GetSymbol(), atom_to_feature_vector(atom)[0])
|
|
65
|
+
atom_features_list.append(atom_to_feature_vector(atom))
|
|
66
|
+
|
|
67
|
+
x = np.array(atom_features_list, dtype=np.int64)
|
|
68
|
+
|
|
69
|
+
# bonds
|
|
70
|
+
num_bond_features = 3 # bond type, bond stereo, is_conjugated
|
|
71
|
+
if len(mol.GetBonds()) > 0: # mol has bonds
|
|
72
|
+
edges_list = []
|
|
73
|
+
edge_features_list = []
|
|
74
|
+
for bond in mol.GetBonds():
|
|
75
|
+
i = bond.GetBeginAtomIdx()
|
|
76
|
+
j = bond.GetEndAtomIdx()
|
|
77
|
+
edge_feature = bond_to_feature_vector(bond)
|
|
78
|
+
# add edges in both directions
|
|
79
|
+
edges_list.append((i, j))
|
|
80
|
+
edge_features_list.append(edge_feature)
|
|
81
|
+
edges_list.append((j, i))
|
|
82
|
+
edge_features_list.append(edge_feature)
|
|
83
|
+
|
|
84
|
+
# data.edge_index: Graph connectivity in COO format with shape [2, num_edges]
|
|
85
|
+
edge_index = np.array(edges_list, dtype=np.int64).T
|
|
86
|
+
|
|
87
|
+
# data.edge_attr: Edge feature matrix with shape [num_edges, num_edge_features]
|
|
88
|
+
edge_attr = np.array(edge_features_list, dtype=np.int64)
|
|
89
|
+
|
|
90
|
+
else: # mol has no bonds
|
|
91
|
+
edge_index = np.empty((2, 0), dtype=np.int64)
|
|
92
|
+
edge_attr = np.empty((0, num_bond_features), dtype=np.int64)
|
|
93
|
+
|
|
94
|
+
graph = dict()
|
|
95
|
+
graph["edge_index"] = edge_index
|
|
96
|
+
graph["edge_feat"] = edge_attr
|
|
97
|
+
graph["node_feat"] = x
|
|
98
|
+
graph["num_nodes"] = len(x)
|
|
99
|
+
|
|
100
|
+
# Handle properties and augmented properties
|
|
101
|
+
props_list = []
|
|
102
|
+
if properties is not None:
|
|
103
|
+
props_list.append(np.array(properties, dtype=np.float32))
|
|
104
|
+
if augmented_properties is not None:
|
|
105
|
+
aug_props = get_augmented_property(mol, augmented_properties)
|
|
106
|
+
if aug_props:
|
|
107
|
+
props_list.append(np.array(aug_props, dtype=np.float32))
|
|
108
|
+
if props_list:
|
|
109
|
+
combined_props = np.concatenate(props_list)
|
|
110
|
+
graph['y'] = combined_props.reshape(1, -1)
|
|
111
|
+
else:
|
|
112
|
+
graph['y'] = np.full((1, 1), np.nan, dtype=np.float32)
|
|
113
|
+
|
|
114
|
+
# Handle augmented features
|
|
115
|
+
if augmented_features is not None:
|
|
116
|
+
graph['morgan'] = add_fingerprint_feature(
|
|
117
|
+
mol,
|
|
118
|
+
'morgan' if 'morgan' in augmented_features else None,
|
|
119
|
+
getmorganfingerprint
|
|
120
|
+
)
|
|
121
|
+
graph['maccs'] = add_fingerprint_feature(
|
|
122
|
+
mol,
|
|
123
|
+
'maccs' if 'maccs' in augmented_features else None,
|
|
124
|
+
getmaccsfingerprint
|
|
125
|
+
)
|
|
126
|
+
else:
|
|
127
|
+
graph['morgan'] = None
|
|
128
|
+
graph['maccs'] = None
|
|
129
|
+
|
|
130
|
+
return graph
|
|
131
|
+
|
|
132
|
+
# except Exception as e:
|
|
133
|
+
# print(f"Error: {e} during converting {smiles_or_mol} to graph")
|
|
134
|
+
# return None
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
from rdkit import Chem, RDLogger
|
|
2
|
+
|
|
3
|
+
RDLogger.DisableLog("rdApp.*")
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import random
|
|
7
|
+
import logging
|
|
8
|
+
from rdkit import Chem
|
|
9
|
+
from typing import List, Tuple, Optional
|
|
10
|
+
random.seed(0)
|
|
11
|
+
import torch
|
|
12
|
+
|
|
13
|
+
bond_dict = [
|
|
14
|
+
None,
|
|
15
|
+
Chem.rdchem.BondType.SINGLE,
|
|
16
|
+
Chem.rdchem.BondType.DOUBLE,
|
|
17
|
+
Chem.rdchem.BondType.TRIPLE,
|
|
18
|
+
Chem.rdchem.BondType.AROMATIC,
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
ATOM_VALENCY = {6: 4, 7: 3, 8: 2, 9: 1, 15: 3, 16: 2, 17: 1, 35: 1, 53: 1}
|
|
22
|
+
|
|
23
|
+
def graph_to_smiles(molecule_list: List[Tuple], atom_decoder: list) -> List[Optional[str]]:
|
|
24
|
+
smiles_list = []
|
|
25
|
+
for index, graph in enumerate(molecule_list):
|
|
26
|
+
try:
|
|
27
|
+
atom_types, edge_types = graph
|
|
28
|
+
mol_init = build_molecule_with_partial_charges(atom_types, edge_types, atom_decoder)
|
|
29
|
+
|
|
30
|
+
# Try to correct the molecule with connection=True, then False if needed
|
|
31
|
+
for connection in (True, False):
|
|
32
|
+
mol_conn, _ = correct_mol(mol_init, connection=connection)
|
|
33
|
+
if mol_conn is not None:
|
|
34
|
+
break
|
|
35
|
+
else:
|
|
36
|
+
mol_conn = mol_init # Fallback to initial molecule
|
|
37
|
+
|
|
38
|
+
# Convert to SMILES
|
|
39
|
+
smiles = mol2smiles(mol_conn)
|
|
40
|
+
if not smiles:
|
|
41
|
+
smiles = Chem.MolToSmiles(mol_conn)
|
|
42
|
+
|
|
43
|
+
if smiles:
|
|
44
|
+
mol = get_mol(smiles)
|
|
45
|
+
if mol is not None:
|
|
46
|
+
# Get the largest fragment
|
|
47
|
+
mol_frags = Chem.rdmolops.GetMolFrags(mol, asMols=True, sanitizeFrags=False)
|
|
48
|
+
largest_mol = max(mol_frags, key=lambda m: m.GetNumAtoms())
|
|
49
|
+
|
|
50
|
+
largest_smiles = mol2smiles(largest_mol)
|
|
51
|
+
if largest_smiles and len(largest_smiles) > 1:
|
|
52
|
+
smiles_list.append(largest_smiles)
|
|
53
|
+
else:
|
|
54
|
+
smiles_list.append(None)
|
|
55
|
+
else:
|
|
56
|
+
smiles_list.append(None)
|
|
57
|
+
else:
|
|
58
|
+
smiles_list.append(None)
|
|
59
|
+
|
|
60
|
+
except Exception as e:
|
|
61
|
+
try:
|
|
62
|
+
# Fallback to RDKit's MolToSmiles if everything else fails
|
|
63
|
+
fallback_smiles = Chem.MolToSmiles(mol_init)
|
|
64
|
+
if fallback_smiles:
|
|
65
|
+
smiles_list.append(fallback_smiles)
|
|
66
|
+
else:
|
|
67
|
+
smiles_list.append(None)
|
|
68
|
+
except Exception as e2:
|
|
69
|
+
smiles_list.append(None)
|
|
70
|
+
|
|
71
|
+
return smiles_list
|
|
72
|
+
|
|
73
|
+
def build_molecule_with_partial_charges(atom_types, edge_types, atom_decoder, verbose=False):
|
|
74
|
+
if verbose:
|
|
75
|
+
print("\nbuilding new molecule")
|
|
76
|
+
|
|
77
|
+
mol = Chem.RWMol()
|
|
78
|
+
for atom in atom_types:
|
|
79
|
+
a = Chem.Atom(atom_decoder[atom.item()])
|
|
80
|
+
mol.AddAtom(a)
|
|
81
|
+
if verbose:
|
|
82
|
+
print("Atom added: ", atom.item(), atom_decoder[atom.item()])
|
|
83
|
+
|
|
84
|
+
edge_types = torch.triu(edge_types)
|
|
85
|
+
all_bonds = torch.nonzero(edge_types)
|
|
86
|
+
|
|
87
|
+
for i, bond in enumerate(all_bonds):
|
|
88
|
+
if bond[0].item() != bond[1].item():
|
|
89
|
+
mol.AddBond(
|
|
90
|
+
bond[0].item(),
|
|
91
|
+
bond[1].item(),
|
|
92
|
+
bond_dict[edge_types[bond[0], bond[1]].item()],
|
|
93
|
+
)
|
|
94
|
+
if verbose:
|
|
95
|
+
print(
|
|
96
|
+
"bond added:",
|
|
97
|
+
bond[0].item(),
|
|
98
|
+
bond[1].item(),
|
|
99
|
+
edge_types[bond[0], bond[1]].item(),
|
|
100
|
+
bond_dict[edge_types[bond[0], bond[1]].item()],
|
|
101
|
+
)
|
|
102
|
+
# add formal charge to atom: e.g. [O+], [N+], [S+]
|
|
103
|
+
# not support [O-], [N-], [S-], [NH+] etc.
|
|
104
|
+
flag, atomid_valence = check_valency(mol)
|
|
105
|
+
if verbose:
|
|
106
|
+
print("flag, valence", flag, atomid_valence)
|
|
107
|
+
if flag:
|
|
108
|
+
continue
|
|
109
|
+
else:
|
|
110
|
+
if len(atomid_valence) == 2:
|
|
111
|
+
idx = atomid_valence[0]
|
|
112
|
+
v = atomid_valence[1]
|
|
113
|
+
an = mol.GetAtomWithIdx(idx).GetAtomicNum()
|
|
114
|
+
if verbose:
|
|
115
|
+
print("atomic num of atom with a large valence", an)
|
|
116
|
+
if an in (7, 8, 16) and (v - ATOM_VALENCY[an]) == 1:
|
|
117
|
+
mol.GetAtomWithIdx(idx).SetFormalCharge(1)
|
|
118
|
+
# print("Formal charge added")
|
|
119
|
+
else:
|
|
120
|
+
continue
|
|
121
|
+
return mol
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def correct_mol(mol, connection=False):
|
|
125
|
+
#####
|
|
126
|
+
no_correct = False
|
|
127
|
+
flag, _ = check_valency(mol)
|
|
128
|
+
if flag:
|
|
129
|
+
no_correct = True
|
|
130
|
+
|
|
131
|
+
while True:
|
|
132
|
+
if connection:
|
|
133
|
+
mol_conn = connect_fragments(mol)
|
|
134
|
+
mol = mol_conn
|
|
135
|
+
if mol is None:
|
|
136
|
+
return None, no_correct
|
|
137
|
+
flag, atomid_valence = check_valency(mol)
|
|
138
|
+
if flag:
|
|
139
|
+
break
|
|
140
|
+
else:
|
|
141
|
+
try:
|
|
142
|
+
assert len(atomid_valence) == 2
|
|
143
|
+
idx = atomid_valence[0]
|
|
144
|
+
v = atomid_valence[1]
|
|
145
|
+
queue = []
|
|
146
|
+
check_idx = 0
|
|
147
|
+
for b in mol.GetAtomWithIdx(idx).GetBonds():
|
|
148
|
+
type = int(b.GetBondType())
|
|
149
|
+
queue.append(
|
|
150
|
+
(b.GetIdx(), type, b.GetBeginAtomIdx(), b.GetEndAtomIdx())
|
|
151
|
+
)
|
|
152
|
+
if type == 12:
|
|
153
|
+
check_idx += 1
|
|
154
|
+
queue.sort(key=lambda tup: tup[1], reverse=True)
|
|
155
|
+
|
|
156
|
+
if queue[-1][1] == 12:
|
|
157
|
+
return None, no_correct
|
|
158
|
+
elif len(queue) > 0:
|
|
159
|
+
start = queue[check_idx][2]
|
|
160
|
+
end = queue[check_idx][3]
|
|
161
|
+
t = queue[check_idx][1] - 1
|
|
162
|
+
mol.RemoveBond(start, end)
|
|
163
|
+
if t >= 1:
|
|
164
|
+
mol.AddBond(start, end, bond_dict[t])
|
|
165
|
+
except Exception as e:
|
|
166
|
+
# print(f"An error occurred in correction: {e}")
|
|
167
|
+
return None, no_correct
|
|
168
|
+
return mol, no_correct
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def check_valid(smiles):
|
|
172
|
+
mol = get_mol(smiles)
|
|
173
|
+
if mol is None:
|
|
174
|
+
return False
|
|
175
|
+
smiles = mol2smiles(mol)
|
|
176
|
+
if smiles is None:
|
|
177
|
+
return False
|
|
178
|
+
return True
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def get_mol(smiles_or_mol):
|
|
182
|
+
"""
|
|
183
|
+
Loads SMILES/molecule into RDKit's object
|
|
184
|
+
"""
|
|
185
|
+
if isinstance(smiles_or_mol, str):
|
|
186
|
+
if len(smiles_or_mol) == 0:
|
|
187
|
+
return None
|
|
188
|
+
mol = Chem.MolFromSmiles(smiles_or_mol)
|
|
189
|
+
if mol is None:
|
|
190
|
+
return None
|
|
191
|
+
try:
|
|
192
|
+
Chem.SanitizeMol(mol)
|
|
193
|
+
except ValueError:
|
|
194
|
+
return None
|
|
195
|
+
return mol
|
|
196
|
+
return smiles_or_mol
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def mol2smiles(mol):
|
|
200
|
+
if mol is None:
|
|
201
|
+
return None
|
|
202
|
+
try:
|
|
203
|
+
Chem.SanitizeMol(mol)
|
|
204
|
+
except ValueError:
|
|
205
|
+
return None
|
|
206
|
+
return Chem.MolToSmiles(mol)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def check_valency(mol):
|
|
210
|
+
try:
|
|
211
|
+
# First attempt to sanitize with specific properties
|
|
212
|
+
Chem.SanitizeMol(mol, sanitizeOps=Chem.SanitizeFlags.SANITIZE_PROPERTIES)
|
|
213
|
+
return True, None
|
|
214
|
+
except ValueError as e:
|
|
215
|
+
e = str(e)
|
|
216
|
+
p = e.find("#")
|
|
217
|
+
e_sub = e[p:]
|
|
218
|
+
atomid_valence = list(map(int, re.findall(r"\d+", e_sub)))
|
|
219
|
+
return False, atomid_valence
|
|
220
|
+
except Exception as e:
|
|
221
|
+
# print(f"An unexpected error occurred: {e}")
|
|
222
|
+
return False, []
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
##### connect fragements
|
|
226
|
+
def select_atom_with_available_valency(frag):
|
|
227
|
+
atoms = list(frag.GetAtoms())
|
|
228
|
+
random.shuffle(atoms)
|
|
229
|
+
for atom in atoms:
|
|
230
|
+
if atom.GetAtomicNum() > 1 and atom.GetImplicitValence() > 0:
|
|
231
|
+
return atom
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def select_atoms_with_available_valency(frag):
|
|
236
|
+
return [
|
|
237
|
+
atom
|
|
238
|
+
for atom in frag.GetAtoms()
|
|
239
|
+
if atom.GetAtomicNum() > 1 and atom.GetImplicitValence() > 0
|
|
240
|
+
]
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def try_to_connect_fragments(combined_mol, frag, atom1, atom2):
|
|
244
|
+
# Make copies of the molecules to try the connection
|
|
245
|
+
trial_combined_mol = Chem.RWMol(combined_mol)
|
|
246
|
+
trial_frag = Chem.RWMol(frag)
|
|
247
|
+
|
|
248
|
+
# Add the new fragment to the combined molecule with new indices
|
|
249
|
+
new_indices = {
|
|
250
|
+
atom.GetIdx(): trial_combined_mol.AddAtom(atom)
|
|
251
|
+
for atom in trial_frag.GetAtoms()
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
# Add the bond between the suitable atoms from each fragment
|
|
255
|
+
trial_combined_mol.AddBond(
|
|
256
|
+
atom1.GetIdx(), new_indices[atom2.GetIdx()], Chem.BondType.SINGLE
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
# Adjust the hydrogen count of the connected atoms
|
|
260
|
+
for atom_idx in [atom1.GetIdx(), new_indices[atom2.GetIdx()]]:
|
|
261
|
+
atom = trial_combined_mol.GetAtomWithIdx(atom_idx)
|
|
262
|
+
num_h = atom.GetTotalNumHs()
|
|
263
|
+
atom.SetNumExplicitHs(max(0, num_h - 1))
|
|
264
|
+
|
|
265
|
+
# Add bonds for the new fragment
|
|
266
|
+
for bond in trial_frag.GetBonds():
|
|
267
|
+
trial_combined_mol.AddBond(
|
|
268
|
+
new_indices[bond.GetBeginAtomIdx()],
|
|
269
|
+
new_indices[bond.GetEndAtomIdx()],
|
|
270
|
+
bond.GetBondType(),
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# Convert to a Mol object and try to sanitize it
|
|
274
|
+
new_mol = Chem.Mol(trial_combined_mol)
|
|
275
|
+
try:
|
|
276
|
+
Chem.SanitizeMol(new_mol)
|
|
277
|
+
return new_mol # Return the new valid molecule
|
|
278
|
+
except Chem.MolSanitizeException:
|
|
279
|
+
return None # If the molecule is not valid, return None
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def connect_fragments(mol):
|
|
283
|
+
# Get the separate fragments
|
|
284
|
+
frags = Chem.GetMolFrags(mol, asMols=True, sanitizeFrags=False)
|
|
285
|
+
if len(frags) < 2:
|
|
286
|
+
return mol
|
|
287
|
+
|
|
288
|
+
combined_mol = Chem.RWMol(frags[0])
|
|
289
|
+
|
|
290
|
+
for frag in frags[1:]:
|
|
291
|
+
# Select all atoms with available valency from both molecules
|
|
292
|
+
atoms1 = select_atoms_with_available_valency(combined_mol)
|
|
293
|
+
atoms2 = select_atoms_with_available_valency(frag)
|
|
294
|
+
|
|
295
|
+
# Try to connect using all combinations of available valency atoms
|
|
296
|
+
for atom1 in atoms1:
|
|
297
|
+
for atom2 in atoms2:
|
|
298
|
+
new_mol = try_to_connect_fragments(combined_mol, frag, atom1, atom2)
|
|
299
|
+
if new_mol is not None:
|
|
300
|
+
# If a valid connection is made, update the combined molecule and break
|
|
301
|
+
combined_mol = new_mol
|
|
302
|
+
break
|
|
303
|
+
else:
|
|
304
|
+
# Continue if the inner loop didn't break (no valid connection found for atom1)
|
|
305
|
+
continue
|
|
306
|
+
# Break if the inner loop did break (valid connection found)
|
|
307
|
+
break
|
|
308
|
+
else:
|
|
309
|
+
# If no valid connections could be made with any of the atoms, return None
|
|
310
|
+
return None
|
|
311
|
+
|
|
312
|
+
return combined_mol
|