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.
Files changed (148) hide show
  1. torch_molecule/__init__.py +70 -0
  2. torch_molecule/base/__init__.py +8 -0
  3. torch_molecule/base/base.py +396 -0
  4. torch_molecule/base/encoder.py +44 -0
  5. torch_molecule/base/generator.py +24 -0
  6. torch_molecule/base/old_encoder.py +322 -0
  7. torch_molecule/base/old_predictor.py +904 -0
  8. torch_molecule/base/old_predictor2.py +436 -0
  9. torch_molecule/base/predictor.py +112 -0
  10. torch_molecule/encoder/attrmask/__init__.py +3 -0
  11. torch_molecule/encoder/attrmask/model.py +113 -0
  12. torch_molecule/encoder/attrmask/modeling_attrmask.py +304 -0
  13. torch_molecule/encoder/constant.py +13 -0
  14. torch_molecule/encoder/contextpred/__init__.py +3 -0
  15. torch_molecule/encoder/contextpred/model.py +207 -0
  16. torch_molecule/encoder/contextpred/modeling_contextpred.py +299 -0
  17. torch_molecule/encoder/contextpred/utils.py +206 -0
  18. torch_molecule/encoder/edgepred/__init__.py +3 -0
  19. torch_molecule/encoder/edgepred/model.py +104 -0
  20. torch_molecule/encoder/edgepred/modeling_edgepred.py +285 -0
  21. torch_molecule/encoder/graphmae/__init__.py +3 -0
  22. torch_molecule/encoder/graphmae/dataloader.py +206 -0
  23. torch_molecule/encoder/graphmae/model.py +137 -0
  24. torch_molecule/encoder/graphmae/modeling_graphmae.py +323 -0
  25. torch_molecule/encoder/infograph/__init__.py +3 -0
  26. torch_molecule/encoder/infograph/loss_function.py +128 -0
  27. torch_molecule/encoder/infograph/model.py +92 -0
  28. torch_molecule/encoder/infograph/modeling_infograph.py +301 -0
  29. torch_molecule/encoder/infograph/module.py +31 -0
  30. torch_molecule/encoder/moama/__init__.py +3 -0
  31. torch_molecule/encoder/moama/model.py +131 -0
  32. torch_molecule/encoder/moama/modeling_moama.py +307 -0
  33. torch_molecule/encoder/moama/utils.py +143 -0
  34. torch_molecule/encoder/pretrained/__init__.py +3 -0
  35. torch_molecule/encoder/pretrained/modeling_pretrained.py +274 -0
  36. torch_molecule/encoder/supervised/__init__.py +3 -0
  37. torch_molecule/encoder/supervised/model.py +97 -0
  38. torch_molecule/encoder/supervised/modeling_supervised.py +354 -0
  39. torch_molecule/generator/digress/__init__.py +3 -0
  40. torch_molecule/generator/digress/diffusion.py +194 -0
  41. torch_molecule/generator/digress/modeling_digress.py +503 -0
  42. torch_molecule/generator/digress/transformer.py +360 -0
  43. torch_molecule/generator/digress/utils.py +196 -0
  44. torch_molecule/generator/gdss/__init__.py +3 -0
  45. torch_molecule/generator/gdss/layers.py +257 -0
  46. torch_molecule/generator/gdss/model.py +204 -0
  47. torch_molecule/generator/gdss/modeling_gdss.py +474 -0
  48. torch_molecule/generator/gdss/sde.py +334 -0
  49. torch_molecule/generator/gdss/solver.py +227 -0
  50. torch_molecule/generator/gdss/toremove/ScoreNetworkA.py +167 -0
  51. torch_molecule/generator/gdss/toremove/ScoreNetworkX.py +85 -0
  52. torch_molecule/generator/gdss/utils.py +240 -0
  53. torch_molecule/generator/graph_dit/__init__.py +3 -0
  54. torch_molecule/generator/graph_dit/diffusion.py +188 -0
  55. torch_molecule/generator/graph_dit/modeling_graph_dit.py +581 -0
  56. torch_molecule/generator/graph_dit/transformer.py +207 -0
  57. torch_molecule/generator/graph_dit/utils.py +220 -0
  58. torch_molecule/generator/graph_ga/__init__.py +3 -0
  59. torch_molecule/generator/graph_ga/crossover.py +182 -0
  60. torch_molecule/generator/graph_ga/modeling_graph_ga.py +310 -0
  61. torch_molecule/generator/graph_ga/mutate.py +136 -0
  62. torch_molecule/generator/graph_ga/oracle.py +95 -0
  63. torch_molecule/generator/jtvae/__init__.py +3 -0
  64. torch_molecule/generator/jtvae/jtnn/chemutils.py +450 -0
  65. torch_molecule/generator/jtvae/jtnn/datautils.py +127 -0
  66. torch_molecule/generator/jtvae/jtnn/jtmpn.py +140 -0
  67. torch_molecule/generator/jtvae/jtnn/jtnn_dec.py +350 -0
  68. torch_molecule/generator/jtvae/jtnn/jtnn_enc.py +131 -0
  69. torch_molecule/generator/jtvae/jtnn/mol_tree.py +111 -0
  70. torch_molecule/generator/jtvae/jtnn/mpn.py +129 -0
  71. torch_molecule/generator/jtvae/jtnn/nnutils.py +66 -0
  72. torch_molecule/generator/jtvae/jtnn/vocab.py +31 -0
  73. torch_molecule/generator/jtvae/jtnn_vae.py +246 -0
  74. torch_molecule/generator/jtvae/modeling_jtvae.py +247 -0
  75. torch_molecule/generator/lstm/__init__.py +3 -0
  76. torch_molecule/generator/lstm/action_sampler.py +91 -0
  77. torch_molecule/generator/lstm/lstm.py +59 -0
  78. torch_molecule/generator/lstm/modeling_lstm.py +263 -0
  79. torch_molecule/generator/lstm/smiles_char_dict.py +123 -0
  80. torch_molecule/generator/lstm/utils.py +62 -0
  81. torch_molecule/generator/molgpt/__init__.py +3 -0
  82. torch_molecule/generator/molgpt/dataset.py +87 -0
  83. torch_molecule/generator/molgpt/gpt.py +251 -0
  84. torch_molecule/generator/molgpt/modeling_molgpt.py +427 -0
  85. torch_molecule/generator/pretrained/modeling_pretrained.py +1 -0
  86. torch_molecule/nn/__init__.py +16 -0
  87. torch_molecule/nn/attention.py +63 -0
  88. torch_molecule/nn/embedder.py +183 -0
  89. torch_molecule/nn/gnn.py +631 -0
  90. torch_molecule/nn/mlp.py +34 -0
  91. torch_molecule/predictor/bfgnn/__init__.py +3 -0
  92. torch_molecule/predictor/bfgnn/model.py +116 -0
  93. torch_molecule/predictor/bfgnn/modeling_bfgnn.py +81 -0
  94. torch_molecule/predictor/dir/__init__.py +3 -0
  95. torch_molecule/predictor/dir/model.py +235 -0
  96. torch_molecule/predictor/dir/modeling_dir.py +161 -0
  97. torch_molecule/predictor/dir/utils.py +40 -0
  98. torch_molecule/predictor/gnn/__init__.py +3 -0
  99. torch_molecule/predictor/gnn/model.py +114 -0
  100. torch_molecule/predictor/gnn/modeling_gnn.py +706 -0
  101. torch_molecule/predictor/gnn/old_modeling_gnn.py +707 -0
  102. torch_molecule/predictor/grea/__init__.py +3 -0
  103. torch_molecule/predictor/grea/model.py +190 -0
  104. torch_molecule/predictor/grea/modeling_grea.py +107 -0
  105. torch_molecule/predictor/grea/old1_features.py +229 -0
  106. torch_molecule/predictor/grea/old1_modeling_grea.py +493 -0
  107. torch_molecule/predictor/grea/utils.py +97 -0
  108. torch_molecule/predictor/grin/__init__.py +3 -0
  109. torch_molecule/predictor/grin/model.py +116 -0
  110. torch_molecule/predictor/grin/modeling_grin.py +91 -0
  111. torch_molecule/predictor/irm/__init__.py +3 -0
  112. torch_molecule/predictor/irm/model.py +127 -0
  113. torch_molecule/predictor/irm/modeling_irm.py +179 -0
  114. torch_molecule/predictor/lstm/__init__.py +3 -0
  115. torch_molecule/predictor/lstm/model.py +145 -0
  116. torch_molecule/predictor/lstm/modeling_lstm.py +627 -0
  117. torch_molecule/predictor/lstm/token_from_smiles.py +85 -0
  118. torch_molecule/predictor/old_modeling_graph.py +650 -0
  119. torch_molecule/predictor/rpgnn/__init__.py +3 -0
  120. torch_molecule/predictor/rpgnn/model.py +185 -0
  121. torch_molecule/predictor/rpgnn/modeling_rpgnn.py +76 -0
  122. torch_molecule/predictor/sgir/__init__.py +3 -0
  123. torch_molecule/predictor/sgir/modeling_sgir.py +260 -0
  124. torch_molecule/predictor/sgir/strategy.py +266 -0
  125. torch_molecule/predictor/smiles_transformer/__init__.py +3 -0
  126. torch_molecule/predictor/smiles_transformer/model.py +424 -0
  127. torch_molecule/predictor/smiles_transformer/modeling_transformer.py +252 -0
  128. torch_molecule/predictor/ssr/__init__.py +3 -0
  129. torch_molecule/predictor/ssr/model.py +283 -0
  130. torch_molecule/predictor/ssr/modeling_ssr.py +267 -0
  131. torch_molecule/predictor/ssr/utils.py +185 -0
  132. torch_molecule/utils/__init__.py +31 -0
  133. torch_molecule/utils/checker.py +158 -0
  134. torch_molecule/utils/checkpoint.py +294 -0
  135. torch_molecule/utils/format.py +140 -0
  136. torch_molecule/utils/generic/metrics.py +530 -0
  137. torch_molecule/utils/generic/pseudo_tasks.py +11 -0
  138. torch_molecule/utils/generic/weights.py +30 -0
  139. torch_molecule/utils/graph/features.py +178 -0
  140. torch_molecule/utils/graph/graph_from_smiles.py +134 -0
  141. torch_molecule/utils/graph/graph_to_smiles.py +312 -0
  142. torch_molecule/utils/hf.py +232 -0
  143. torch_molecule/utils/search.py +64 -0
  144. torch_molecule-0.1.0.dev2.dist-info/METADATA +243 -0
  145. torch_molecule-0.1.0.dev2.dist-info/RECORD +148 -0
  146. torch_molecule-0.1.0.dev2.dist-info/WHEEL +5 -0
  147. torch_molecule-0.1.0.dev2.dist-info/licenses/LICENSE +21 -0
  148. 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