D4CMPP2 0.4.0__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.
- D4CMPP2/_Data/AGENTS.md +24 -0
- D4CMPP2/_Data/Aqsoldb.csv +9291 -0
- D4CMPP2/_Data/BradleyMP.csv +3042 -0
- D4CMPP2/_Data/Lipophilicity.csv +1131 -0
- D4CMPP2/_Data/README.md +8 -0
- D4CMPP2/_Data/__init__.py +26 -0
- D4CMPP2/_Data/optical.csv +20237 -0
- D4CMPP2/_Data/test.csv +190 -0
- D4CMPP2/__init__.py +16 -0
- D4CMPP2/__main__.py +5 -0
- D4CMPP2/_main.py +500 -0
- D4CMPP2/cli.py +7 -0
- D4CMPP2/exceptions.py +53 -0
- D4CMPP2/grid_search.py +259 -0
- D4CMPP2/network_refer.yaml +160 -0
- D4CMPP2/networks/AFP_model.py +72 -0
- D4CMPP2/networks/AFPwithSolv_model.py +72 -0
- D4CMPP2/networks/DMPNN_model.py +90 -0
- D4CMPP2/networks/DMPNNwithSolv_model.py +89 -0
- D4CMPP2/networks/GAT_model.py +48 -0
- D4CMPP2/networks/GATwithSolv_model.py +63 -0
- D4CMPP2/networks/GCN_model.py +113 -0
- D4CMPP2/networks/GCNwithSolv_model.py +103 -0
- D4CMPP2/networks/GC_model.py +122 -0
- D4CMPP2/networks/ISATPM_model.py +14 -0
- D4CMPP2/networks/ISATPN_model.py +199 -0
- D4CMPP2/networks/ISAT_model.py +90 -0
- D4CMPP2/networks/MPNN_model.py +56 -0
- D4CMPP2/networks/MPNNwithSolv_model.py +72 -0
- D4CMPP2/networks/__init__.py +25 -0
- D4CMPP2/networks/base.py +250 -0
- D4CMPP2/networks/registry.py +187 -0
- D4CMPP2/networks/src/AFP.py +118 -0
- D4CMPP2/networks/src/BiDropout.py +29 -0
- D4CMPP2/networks/src/DMPNN.py +35 -0
- D4CMPP2/networks/src/GAT.py +69 -0
- D4CMPP2/networks/src/GC.py +85 -0
- D4CMPP2/networks/src/GCN.py +71 -0
- D4CMPP2/networks/src/ISAT.py +153 -0
- D4CMPP2/networks/src/Linear.py +49 -0
- D4CMPP2/networks/src/MPNN.py +56 -0
- D4CMPP2/networks/src/SolventLayer.py +62 -0
- D4CMPP2/networks/src/__init__.py +0 -0
- D4CMPP2/networks/src/distGCN.py +21 -0
- D4CMPP2/networks/src/pyg_hetero.py +24 -0
- D4CMPP2/optimize.py +472 -0
- D4CMPP2/src/Analyzer/ISAAnalyzer.py +458 -0
- D4CMPP2/src/Analyzer/ISAPNAnalyzer.py +366 -0
- D4CMPP2/src/Analyzer/ISAwSAnalyzer.py +117 -0
- D4CMPP2/src/Analyzer/MolAnalyzer.py +319 -0
- D4CMPP2/src/Analyzer/__init__.py +54 -0
- D4CMPP2/src/Analyzer/core.py +480 -0
- D4CMPP2/src/Analyzer/factory.py +166 -0
- D4CMPP2/src/Analyzer/interpretation.py +232 -0
- D4CMPP2/src/Analyzer/results.py +101 -0
- D4CMPP2/src/DataManager/Dataset/GraphDataset.py +314 -0
- D4CMPP2/src/DataManager/Dataset/ISAGraphDataset.py +384 -0
- D4CMPP2/src/DataManager/Dataset/__init__.py +0 -0
- D4CMPP2/src/DataManager/GraphGenerator/ISAGraphGenerator.py +223 -0
- D4CMPP2/src/DataManager/GraphGenerator/MolGraphGenerator.py +73 -0
- D4CMPP2/src/DataManager/GraphGenerator/__init__.py +14 -0
- D4CMPP2/src/DataManager/ISADataManager.py +67 -0
- D4CMPP2/src/DataManager/MolDataManager.py +735 -0
- D4CMPP2/src/DataManager/__init__.py +14 -0
- D4CMPP2/src/DataManager/contracts.py +179 -0
- D4CMPP2/src/NetworkManager/ISANetworkManager.py +12 -0
- D4CMPP2/src/NetworkManager/NetworkManager.py +520 -0
- D4CMPP2/src/NetworkManager/__init__.py +14 -0
- D4CMPP2/src/PostProcessor.py +160 -0
- D4CMPP2/src/TrainManager/ISATrainManager.py +26 -0
- D4CMPP2/src/TrainManager/TrainManager.py +254 -0
- D4CMPP2/src/TrainManager/__init__.py +14 -0
- D4CMPP2/src/TrainManager/callbacks.py +119 -0
- D4CMPP2/src/__init__.py +0 -0
- D4CMPP2/src/utils/PATH.py +246 -0
- D4CMPP2/src/utils/__init__.py +0 -0
- D4CMPP2/src/utils/argparser.py +56 -0
- D4CMPP2/src/utils/checkpointing.py +90 -0
- D4CMPP2/src/utils/config_resolution.py +123 -0
- D4CMPP2/src/utils/config_validation.py +370 -0
- D4CMPP2/src/utils/csv_validation.py +105 -0
- D4CMPP2/src/utils/data_quality.py +181 -0
- D4CMPP2/src/utils/featureizer.py +202 -0
- D4CMPP2/src/utils/functional_group.csv +169 -0
- D4CMPP2/src/utils/graph_cache.py +213 -0
- D4CMPP2/src/utils/leaderboard.py +212 -0
- D4CMPP2/src/utils/metrics.py +31 -0
- D4CMPP2/src/utils/module_loader.py +147 -0
- D4CMPP2/src/utils/output.py +80 -0
- D4CMPP2/src/utils/reproducibility.py +70 -0
- D4CMPP2/src/utils/run_manifest.py +175 -0
- D4CMPP2/src/utils/scaler.py +40 -0
- D4CMPP2/src/utils/sculptor.py +713 -0
- D4CMPP2/src/utils/splitting.py +250 -0
- D4CMPP2/src/utils/supportfile_saver.py +94 -0
- D4CMPP2/src/utils/tools.py +156 -0
- D4CMPP2/src/utils/transfer_learning.py +111 -0
- d4cmpp2-0.4.0.dist-info/METADATA +420 -0
- d4cmpp2-0.4.0.dist-info/RECORD +103 -0
- d4cmpp2-0.4.0.dist-info/WHEEL +5 -0
- d4cmpp2-0.4.0.dist-info/entry_points.txt +2 -0
- d4cmpp2-0.4.0.dist-info/licenses/LICENSE +21 -0
- d4cmpp2-0.4.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import os
|
|
3
|
+
import hashlib
|
|
4
|
+
|
|
5
|
+
import rdkit.Chem as Chem
|
|
6
|
+
from rdkit.Chem import AllChem, MACCSkeys
|
|
7
|
+
|
|
8
|
+
class InvalidAtomError(Exception):
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
def get_graph_features(mol):
|
|
12
|
+
return np.array(MACCSkeys.GenMACCSKeys(mol)).astype(float)
|
|
13
|
+
|
|
14
|
+
def get_atom_features(mol):
|
|
15
|
+
atom_features = []
|
|
16
|
+
for atom in mol.GetAtoms():
|
|
17
|
+
atom_features.append(atom_feature(atom))
|
|
18
|
+
return np.concatenate(atom_features, axis=0)
|
|
19
|
+
|
|
20
|
+
def get_atom_features_and_graph_feature(mol, graph_featurizer=None):
|
|
21
|
+
atom_features = get_atom_features(mol)
|
|
22
|
+
|
|
23
|
+
if graph_featurizer is not None:
|
|
24
|
+
graph_feature = graph_featurizer(mol)
|
|
25
|
+
else:
|
|
26
|
+
graph_feature = get_graph_features(mol)
|
|
27
|
+
graph_feature = np.tile(graph_feature, (atom_features.shape[0],1))
|
|
28
|
+
return atom_features, graph_feature
|
|
29
|
+
|
|
30
|
+
def get_bond_features(mol):
|
|
31
|
+
bond_features = []
|
|
32
|
+
for bond in mol.GetBonds():
|
|
33
|
+
bond_features.append(bond_feature(bond))
|
|
34
|
+
if len(bond_features) == 0:
|
|
35
|
+
return np.zeros((1, 10))
|
|
36
|
+
return np.concatenate(bond_features, axis=0)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def bond_feature(bond):
|
|
40
|
+
features = np.concatenate([bond_type(bond),
|
|
41
|
+
bond_stereo(bond)
|
|
42
|
+
], axis=0).reshape(1,-1)
|
|
43
|
+
|
|
44
|
+
return features
|
|
45
|
+
|
|
46
|
+
def generate_conformer(mol, save_conf = True, xyz_path = None):
|
|
47
|
+
smiles = Chem.MolToSmiles(mol)
|
|
48
|
+
if mol is None:
|
|
49
|
+
return None
|
|
50
|
+
mol = Chem.AddHs(mol)
|
|
51
|
+
AllChem.EmbedMolecule(mol)
|
|
52
|
+
AllChem.EmbedMultipleConfs(mol, 10)
|
|
53
|
+
li = AllChem.UFFOptimizeMoleculeConfs(mol, maxIters=5000)
|
|
54
|
+
eg_li = [e if s==0 else np.inf for s,e in li ]
|
|
55
|
+
idx = eg_li.index(min(eg_li))
|
|
56
|
+
conf = mol.GetConformer(idx)
|
|
57
|
+
if save_conf:
|
|
58
|
+
save_xyz(smiles, conf, xyz_path)
|
|
59
|
+
return conf
|
|
60
|
+
|
|
61
|
+
def get_conformer(mol, do_optimize=False, xyz_path = None):
|
|
62
|
+
smiles = Chem.MolToSmiles(mol)
|
|
63
|
+
if mol is None:
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
if xyz_path is not None:
|
|
67
|
+
pos, atoms = load_xyz(smiles, xyz_path)
|
|
68
|
+
if pos is None and do_optimize:
|
|
69
|
+
return generate_conformer(mol, save_conf=True ,xyz_path = xyz_path)
|
|
70
|
+
elif pos is None:
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
conf = Chem.Conformer()
|
|
74
|
+
for i in range(len(atoms)):
|
|
75
|
+
conf.SetAtomPosition(i, pos[i])
|
|
76
|
+
return conf
|
|
77
|
+
|
|
78
|
+
def save_xyz(smiles, conf, xyz_path):
|
|
79
|
+
mol = Chem.MolFromSmiles(smiles)
|
|
80
|
+
m = hashlib.sha256()
|
|
81
|
+
m.update(smiles.encode('utf-8'))
|
|
82
|
+
name = m.hexdigest()
|
|
83
|
+
result=f'{mol.GetNumAtoms()} #{smiles}\n'
|
|
84
|
+
result+=f'# {Chem.GetFormalCharge(mol)} 1\n'
|
|
85
|
+
for i, atom in enumerate(mol.GetAtoms()):
|
|
86
|
+
positions = conf.GetAtomPosition(i)
|
|
87
|
+
result+=f'{atom.GetSymbol()} {positions.x} {positions.y} {positions.z}\n'
|
|
88
|
+
with open(f'{xyz_path}/{name}.xyz','w') as t:
|
|
89
|
+
t.write(result)
|
|
90
|
+
return name
|
|
91
|
+
|
|
92
|
+
def load_xyz(smiles,xyz_path):
|
|
93
|
+
m = hashlib.sha256()
|
|
94
|
+
m.update(smiles.encode('utf-8'))
|
|
95
|
+
name = m.hexdigest()
|
|
96
|
+
path = os.path.join(xyz_path,name+'.xyz')
|
|
97
|
+
if not os.path.exists(path):
|
|
98
|
+
return None, None
|
|
99
|
+
m = hashlib.sha256()
|
|
100
|
+
m.update(smiles.encode('utf-8'))
|
|
101
|
+
name = m.hexdigest()
|
|
102
|
+
with open(path,'r') as t:
|
|
103
|
+
file= t.read()
|
|
104
|
+
xyzs = file.split("\n")[2:]
|
|
105
|
+
pos = []
|
|
106
|
+
atoms = []
|
|
107
|
+
for xyz in xyzs:
|
|
108
|
+
space = '\t' if '\t' in xyz else ' '
|
|
109
|
+
a = xyz.split(space)
|
|
110
|
+
if len(a)!=4 and len(a)!=5: break
|
|
111
|
+
pos.append(np.array([to_float(a[1]),to_float(a[2]),to_float(a[3])]))
|
|
112
|
+
atoms.append(a[0])
|
|
113
|
+
return np.stack(pos), atoms
|
|
114
|
+
|
|
115
|
+
def to_float(a):
|
|
116
|
+
try:
|
|
117
|
+
return float(a)
|
|
118
|
+
except ValueError as exc:
|
|
119
|
+
if '*^' in a:
|
|
120
|
+
return float(a.replace('*^','e'))
|
|
121
|
+
raise ValueError(
|
|
122
|
+
f"XYZ coordinate {a!r} is not numeric. Check the cached XYZ file."
|
|
123
|
+
) from exc
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def get_bond_features_conformer(mol, conf=None, xyz_path=None):
|
|
127
|
+
if conf is None:
|
|
128
|
+
conf = get_conformer(mol, do_optimize=True, xyz_path=xyz_path)
|
|
129
|
+
|
|
130
|
+
bond_features = []
|
|
131
|
+
for bond in mol.GetBonds():
|
|
132
|
+
bond_features.append(bond_feature_conformer(bond, conf))
|
|
133
|
+
return np.concatenate(bond_features, axis=0)
|
|
134
|
+
|
|
135
|
+
def bond_feature_conformer(bond, conf):
|
|
136
|
+
features = np.concatenate([bond_type(bond),
|
|
137
|
+
bond_stereo(bond),
|
|
138
|
+
bond_length(bond, conf)
|
|
139
|
+
], axis=0).reshape(1,-1)
|
|
140
|
+
return features
|
|
141
|
+
|
|
142
|
+
def bond_length(bond, conf):
|
|
143
|
+
src_pos = conf.GetAtomPosition(bond.GetBeginAtomIdx())
|
|
144
|
+
dst_pos = conf.GetAtomPosition(bond.GetEndAtomIdx())
|
|
145
|
+
return np.array([np.linalg.norm(src_pos-dst_pos)]).astype(float)
|
|
146
|
+
|
|
147
|
+
def atom_feature(atom):
|
|
148
|
+
features = np.concatenate([atom_symbol_HNums(atom),
|
|
149
|
+
atom_degree(atom),
|
|
150
|
+
atom_Aroma(atom),
|
|
151
|
+
atom_Hybrid(atom),
|
|
152
|
+
atom_ring(atom),
|
|
153
|
+
atom_FC(atom)
|
|
154
|
+
], axis=0).reshape(1,-1)
|
|
155
|
+
|
|
156
|
+
return features
|
|
157
|
+
|
|
158
|
+
def bond_type(bond):
|
|
159
|
+
return np.array(one_of_k_encoding(str(bond.GetBondType()),['SINGLE', 'DOUBLE', 'TRIPLE', 'AROMATIC'])).astype(int)
|
|
160
|
+
|
|
161
|
+
def bond_stereo(bond):
|
|
162
|
+
return np.array(one_of_k_encoding(str(bond.GetStereo()),['STEREONONE', 'STEREOANY', 'STEREOZ', 'STEREOE'])).astype(int)
|
|
163
|
+
|
|
164
|
+
def atom_symbol_HNums(atom):
|
|
165
|
+
|
|
166
|
+
return np.array(one_of_k_encoding(atom.GetSymbol(),
|
|
167
|
+
['C', 'N', 'O','S', 'H', 'F', 'Cl', 'Br', 'I', 'Se', 'Te','Si','P','B','Sn','Ge'])+
|
|
168
|
+
one_of_k_encoding(atom.GetTotalNumHs(), [0, 1, 2, 3, 4]))
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def atom_degree(atom):
|
|
172
|
+
return np.array(one_of_k_encoding(atom.GetDegree(), [0, 1, 2, 3, 4, 5 ,6])).astype(int)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def atom_Aroma(atom):
|
|
176
|
+
return np.array([atom.GetIsAromatic()]).astype(int)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def atom_Hybrid(atom):
|
|
180
|
+
return np.array(one_of_k_encoding(str(atom.GetHybridization()),['S','SP','SP2','SP3','SP3D','SP3D2'],'S')).astype(int)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def atom_ring(atom):
|
|
184
|
+
return np.array([atom.IsInRing()]).astype(int)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def atom_FC(atom):
|
|
188
|
+
return np.array(one_of_k_encoding(atom.GetFormalCharge(), [-4,-3,-2,-1, 0, 1, 2, 3, 4])).astype(int)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def one_of_k_encoding(x, allowable_set,default=None):
|
|
192
|
+
if x not in allowable_set:
|
|
193
|
+
if default is not None:
|
|
194
|
+
return list(map(lambda s: default == s, allowable_set))
|
|
195
|
+
raise InvalidAtomError("input {0} not in allowable set{1}:".format(x, allowable_set))
|
|
196
|
+
return list(map(lambda s: x == s, allowable_set))
|
|
197
|
+
|
|
198
|
+
def triplet_feature(mol, conf, atom1, atom2, atom3):
|
|
199
|
+
return np.array([rdMolTransforms.GetAngleRad(conf, atom1, atom2, atom3)]).astype(float)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Name,Formula,SMARTS,Priority,,
|
|
2
|
+
Sulfur,S,[S;!R],-1,,
|
|
3
|
+
Nitrogen,N,[N;!R],-1,,
|
|
4
|
+
Oxygen,O,[O;!R],-1,,
|
|
5
|
+
Alkane,RH,[C;!R],-1,,
|
|
6
|
+
Methyl,C,[CH3],0,,
|
|
7
|
+
Ethyl,CC,[CH2][CH3],0,,
|
|
8
|
+
Prophyl,CCC,[CH2][CH2][CH3],0,,
|
|
9
|
+
Alkene,R2C=CR2,[C;!R]=[C;!R],1,,
|
|
10
|
+
Alkyne,RC≡CR',[C;!R]#[C;!R],1,,
|
|
11
|
+
Allene,R2C=C=CR2,[C;!R]=[C;!R]=[C;!R],1,,
|
|
12
|
+
Cumulene,R2C=C=C=CR2,[C;!R]=[C;!R]=[C;!R]=[C;!R],1,,
|
|
13
|
+
Fluoro,RF,F,1,,
|
|
14
|
+
Chloro,RCl,[Cl],1,,
|
|
15
|
+
Bromo,RBr,[Br],1,,
|
|
16
|
+
Iodo,RI,I,1,,
|
|
17
|
+
Ether,ROR',[OH0;!R],1,,
|
|
18
|
+
Alcohol,ROH,[OH1],1,,
|
|
19
|
+
Aldehyde,RCHO,[CH1;!R]=O,1,,
|
|
20
|
+
Ketone-O,RCOR',[OX1;+0],1,,
|
|
21
|
+
Ketone,RCOR',[CX3;!R]=O,1,,
|
|
22
|
+
Amine,RNH2,[NH2],1,,
|
|
23
|
+
Aldimine / Imine,RCHNH2,[CH1]=[NH1],1,,
|
|
24
|
+
Nitrile / Cyanide,,C#N,1,,
|
|
25
|
+
Isocyanide / Isonitrile,,[N+]#[C-],1,,
|
|
26
|
+
Azide,,N=[N+]=[N-],1,,
|
|
27
|
+
Azo compound / Diimide / Diazene,,[NH0]=[NH0],1,,
|
|
28
|
+
Diazonium salt,,[N+]#N,1,,
|
|
29
|
+
Hydrazine,,[NX3][NX3],1,,
|
|
30
|
+
Enamine,,C=CN,1,,`
|
|
31
|
+
Hydroxylamine,,N[OH1],1,,
|
|
32
|
+
Amine oxide / N-oxide,,[N+;D4][O-],1,,
|
|
33
|
+
Cyanate,,OC#N,1,,
|
|
34
|
+
Isocyanate,,N=C=O,1,,
|
|
35
|
+
Nitro compound,,[N+](=O)[O-],1,,
|
|
36
|
+
Nitroso,,N=O,1,,
|
|
37
|
+
Nitrite,,ON=O,1,,
|
|
38
|
+
Nitrate,,O[N+](=O)[O-],1,,
|
|
39
|
+
Thioether,RSR',[SD2],1,,
|
|
40
|
+
Thiol,RSH,[SH1],1,,
|
|
41
|
+
Thial / Thioaldehyde,RCHS,[CH1](=S),1,,
|
|
42
|
+
Thione / Thioketone,RCSR',[CD3](=S),1,,
|
|
43
|
+
phosphate,,P(=O)(O)(O)(O),1,,
|
|
44
|
+
Carboxylic Acid,RCO2H,[CH0;!R](=O)[OH1],2,,
|
|
45
|
+
Acyl halide,RCOX,"[CH0;!R](=O)[F,Cl,I,Br]",2,,
|
|
46
|
+
Ester,RCO2R',[CH0;!R](=O)[OH0],2,,
|
|
47
|
+
Hydroperoxide,ROOH,[OH0][OH1],2,,
|
|
48
|
+
Peroxide,ROOR',[OH0;!R][OH0;!R],2,,
|
|
49
|
+
Ketene,RC(=C=O)R',[CX3]=C=O,2,,
|
|
50
|
+
Ketimine / Imine,,[CX3]=[NH0],2,,
|
|
51
|
+
Ketenimine,,[CX3]=C=N,2,,
|
|
52
|
+
Carbodiimide,,[NH0]=C=[NH0],2,,
|
|
53
|
+
Amidine,,[CH0;!R](=[NH1])[NH2],2,,
|
|
54
|
+
Amidrazone,,[CH0;!R](=[NH1])[NH1][NH2],2,,
|
|
55
|
+
Guanidine,,N[CH0;!R](=[NH1])[NH1][NH2],2,,
|
|
56
|
+
Hydrazone,,[CX3;!R]=N[NH2],2,,
|
|
57
|
+
Aminal,,[CX4;!R](N)(N),2,,
|
|
58
|
+
Hemiaminal,,[CX4;!R](N)O,2,,
|
|
59
|
+
Aldoxime,,[CH1]=N[OH1],2,,
|
|
60
|
+
Ketoxime,,[CH0;D3]=N[OH1],2,,
|
|
61
|
+
Aldoxime ether,,[CH1]=N[OH0],2,,
|
|
62
|
+
Ketoxime ether,,[CH0;X3]=N[OH0],2,,
|
|
63
|
+
Amide,,[CH0;!R](=O)N,2,,
|
|
64
|
+
Carbamic acid,,NC(=O)[OH1],2,,
|
|
65
|
+
Carbamate,,NC(=O)[OH0],2,,
|
|
66
|
+
Urea,,NC(=O)N,2,,
|
|
67
|
+
Isourea,,NC(=[NH1])O,2,,
|
|
68
|
+
Carbamic acyl halide,,"NC(=O)[F,Cl,I,Br]",2,,
|
|
69
|
+
Acyl cyanide,,[CH0](=O)C#N,2,,
|
|
70
|
+
[[Thioic O-acid]],RC(S)OH,[CH0](=S)[OH1],2,,
|
|
71
|
+
[[Thioic S-acid]],RC(O)SH,[CH0](=O)[SH1],2,,
|
|
72
|
+
Dithioic acid,RCS2H,[CH0](=S)[SH1],2,,
|
|
73
|
+
Thioyl_halide,RCSX,"[CH0](=S)[F,Cl,Br,I]",2,,
|
|
74
|
+
[[Thioic O-ester]],RC(S)OR',[CH0](=S)[OH0],2,,
|
|
75
|
+
[[Thioic S-ester]],RC(O)SR',[CH0](=O)[SH0],2,,
|
|
76
|
+
Dithioic ester,RCS2R',[CH0](=S)[SH0],2,,
|
|
77
|
+
Sulfuric acid,SO3,[SH0](=O)(=O)O,3,,
|
|
78
|
+
Peroxy acid,RCO3H,[CH0;!R](=O)O[OH1],3,,
|
|
79
|
+
Anhydride,RCOOCOR',[CH0;!R](=O)O[CH0](=O),3,,
|
|
80
|
+
Carbonate,ROCOOR',[OH0;!R]C(=O)[OH0],3,,
|
|
81
|
+
Hydrate,RC(OH)(OH)R',[CD4]([OH1])[OH1],3,,
|
|
82
|
+
Hemiacetal,RCH(OR')(OH),[CH1]([OH0])[OH1],3,,
|
|
83
|
+
Acetal,RCH(OR')(OR''),[CH1]([OH0])[OH0],3,,
|
|
84
|
+
Hemiketal,RC(OR'')(OH)R',[CX4]([OH0])[OH1],3,,
|
|
85
|
+
Ketal,RC(OR'')(OR''')R',[CX4]([OH0])[OH0],3,,
|
|
86
|
+
Orthoester,RC(OR')(OR'')(OR'''),[CX4]([OH0])([OH0])[OH0],3,,
|
|
87
|
+
Orthocarbonate,ROC(OR')(OR'')(OR'''),O[CX4](O)(O)O,3,,
|
|
88
|
+
Enol,RC(OH)=CR'2,[CH0;!R]([OH1])=[CD3],3,,
|
|
89
|
+
Enol ether,RC(OR')=CR''2,[CH0;!R]([OH0])=[CD3],3,,
|
|
90
|
+
Hydroxamic acid,,[CH0;!R](=O)[NH0]([OH1]),3,,
|
|
91
|
+
Imide,,[CH0;!R](=O)[NH0][CH0](=O),3,,
|
|
92
|
+
Imidoester,,[CH0;!R](=N)N,3,,
|
|
93
|
+
Imidoyl halide,,"[CH0;!R](=N)[F,Cl,Br,I]",3,,
|
|
94
|
+
Hydrazide,,[CH0;!R](=O)NN,3,,
|
|
95
|
+
Semicarbazide,,[NX3]C(=O)N[NX3],3,,
|
|
96
|
+
Semicarbazone,,[NX3]C(=O)N[NX2],3,,
|
|
97
|
+
α-lactone,,C1(=O)OC1,5,,
|
|
98
|
+
β-lactone,,C1(=O)OCC1,5,,
|
|
99
|
+
γ-lactone,,C1(=O)OCCC1,5,,
|
|
100
|
+
δ-lactone,/,C1(=O)OCCCC1,5,,
|
|
101
|
+
ε-lactone,,C1(=O)OCCCCC1,5,,
|
|
102
|
+
α-Lactam,,C1(=O)[NH1]C1,5,,
|
|
103
|
+
β-Lactam,,C1(=O)[NH1]CC1,5,,
|
|
104
|
+
γ-Lactam,,C1(=O)[NH1]CCC1,5,,
|
|
105
|
+
δ-Lactam,,C1(=O)[NH1]CCCC1,5,,
|
|
106
|
+
ε-Lactam,,C1(=O)[NH1]CCCCC1,5,,
|
|
107
|
+
Epoxide,/,O1CC1,5,,
|
|
108
|
+
ethylenamine,,C1CN1,5,,
|
|
109
|
+
trymethylene oxide,,C1COC1,5,,
|
|
110
|
+
tetrahydrofuran,,C1CCOC1,5,,
|
|
111
|
+
pyrrolidine,,C1CCNC1,5,,
|
|
112
|
+
pyran,,C1=CCOC=C1,5,,
|
|
113
|
+
piperidine,,C1CCNCC1,5,,
|
|
114
|
+
dioxane,,C1COCCO1,5,,
|
|
115
|
+
morpholine,,C1COCCN1,5,,
|
|
116
|
+
Unknown tetra ring,,[R]1@[R]@[R]@[R]@1,5,,
|
|
117
|
+
Unknown penta ring,,[R]1@[R]@[R]@[R]@[R]@1,5,,
|
|
118
|
+
Unknown hexa ring,,[R]1@[R]@[R]@[R]@[R]@[R]@1,5,,
|
|
119
|
+
Unknown septa ring,,[R]1@[R]@[R]@[R]@[R]@[R]@[R]@1,5,,
|
|
120
|
+
Unknown octa ring,,[R]1@[R]@[R]@[R]@[R]@[R]@[R]@[R]@1,5,,
|
|
121
|
+
Unknown nona ring,,[R]1@[R]@[R]@[R]@[R]@[R]@[R]@[R]@[R]@1,5,,
|
|
122
|
+
Unknown deca ring,,[R]1@[R]@[R]@[R]@[R]@[R]@[R]@[R]@[R]@[R]@1,5,,
|
|
123
|
+
Unknown tetra aromatic ring,,[a]1[a][a][a]1,5,,
|
|
124
|
+
Unknown penta aromatic ring,,[a]1[a][a][a][a]1,5,,
|
|
125
|
+
Unknown hexa aromatic ring,,[a]1[a][a][a][a][a]1,5,,
|
|
126
|
+
Unknown septa aromatic ring,,[a]1[a][a][a][a][a][a]1,5,,
|
|
127
|
+
Unknown octa aromatic ring,,[a]1[a][a][a][a][a][a][a]1,5,,
|
|
128
|
+
Unknown nona aromatic ring,,[a]1[a][a][a][a][a][a][a][a]1,5,,
|
|
129
|
+
Unknown deca aromatic ring,,[a]1[a][a][a][a][a][a][a][a][a]1,5,,
|
|
130
|
+
furan,,c1ccoc1,6,,
|
|
131
|
+
thiophene,,c1ccsc1,6,,
|
|
132
|
+
pyrrole,,c1ccnc1,6,,
|
|
133
|
+
Benzene,,c1ccccc1,6,,
|
|
134
|
+
pyridine,,c1ccncc1,6,,
|
|
135
|
+
imidazole,,c1cncn1,6,,
|
|
136
|
+
thiazole,,c1cscn1,6,,
|
|
137
|
+
pyrimidine,,c1cncnc1,6,,
|
|
138
|
+
naphthalene,,c1ccc2ccccc2c1,9,,
|
|
139
|
+
anthracene,,c1ccc2cc3ccccc3cc2c1,9,,
|
|
140
|
+
phenanthrene,,c1ccc2c(c1)ccc1ccccc12,9,,
|
|
141
|
+
chrysene,,c1ccc2c(c1)ccc1c3ccccc3ccc21,9,,
|
|
142
|
+
pyrene,,c1cc2ccc3cccc4ccc(c1)c2c34,9,,
|
|
143
|
+
corannulene,,c1cc2ccc3ccc4ccc5ccc1c1c2c3c4c51,9,,
|
|
144
|
+
coronene,,c1cc2ccc3ccc4ccc5ccc6ccc1c1c2c3c4c5c61,9,,
|
|
145
|
+
pentalene,,C1=CC2=CC=CC2=C1,9,,
|
|
146
|
+
indene,,C1=Cc2ccccc2C1,9,,
|
|
147
|
+
azulene,,c1ccc2cccc-2cc1,9,,
|
|
148
|
+
heptalene,,C1=CC=C2C=CC=CC=C2C=C1,9,,
|
|
149
|
+
biphenylene,,c1ccc2c(c1)-c1ccccc1-2,9,,
|
|
150
|
+
as-indacene,,C1=Cc2c3c(ccc2=C1)=CC=C3,9,,
|
|
151
|
+
s-indacene,,C1=Cc2cc3c(cc2=C1)C=CC=3,9,,
|
|
152
|
+
acenaphthylene,,C1=Cc2cccc3cccc1c23,9,,
|
|
153
|
+
fluorene,,c1ccc2c(c1)Cc1ccccc1-2,9,,
|
|
154
|
+
phenalene,,C1=Cc2cccc3cccc(c23)C1,9,,
|
|
155
|
+
perylene,,c1cc2cccc3c4cccc5cccc(c(c1)c23)c54,9,,
|
|
156
|
+
indole,,c1ccc2nccc2c1,9,,
|
|
157
|
+
isoindole,,c1ccc2cncc2c1,9,,
|
|
158
|
+
indolizine,,c1ccn2cccc2c1,9,,
|
|
159
|
+
quinoline,,c1ccc2ncccc2c1,9,,
|
|
160
|
+
isoquinoline,,c1ccc2cnccc2c1,9,,
|
|
161
|
+
purine,,c1ncc2ncnc2n1,9,,
|
|
162
|
+
carbazole,,c1ccc2c(c1)nc1ccccc12,9,,
|
|
163
|
+
dibenzofuran,,c1ccc2c(c1)oc1ccccc12,9,,
|
|
164
|
+
2H-chromoene,,C1=Cc2ccccc2OC1,9,,
|
|
165
|
+
xanthene,,c1ccc2c(c1)Cc1ccccc1O2,9,,
|
|
166
|
+
porphyrin,,C1=Cc2cc3ccc(cc4nc(cc5ccc(cc1n2)[nH]5)C=C4)[nH]3,9,,
|
|
167
|
+
chlorin,,C1=Cc2cc3ccc(cc4nc(cc5ccc(cc1n2)[nH]5)CC4)[nH]3,9,,
|
|
168
|
+
BODIPY,,F[B-]1(F)N2C=CC=C2C=C2C=CC=[N+]12,9,,
|
|
169
|
+
phthalocyanine,,c1ccc2c(c1)-c1nc-2nc2[nH]c(nc3nc(nc4[nH]c(n1)c1ccccc41)-c1ccccc1-3)c1ccccc21,9,,
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""Versioned graph-cache identity, validation, and atomic persistence."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import importlib.metadata
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import uuid
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import torch
|
|
12
|
+
from torch_geometric.data import Data, HeteroData
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
GRAPH_CACHE_SCHEMA_VERSION = 2
|
|
16
|
+
GRAPH_FEATURE_CONTRACT_VERSION = 1
|
|
17
|
+
ISA_RELATIONS = (
|
|
18
|
+
("r_nd", "r2r", "r_nd"),
|
|
19
|
+
("r_nd", "r2i", "i_nd"),
|
|
20
|
+
("i_nd", "i2i", "i_nd"),
|
|
21
|
+
("i_nd", "i2d", "d_nd"),
|
|
22
|
+
("d_nd", "d2d", "d_nd"),
|
|
23
|
+
("d_nd", "d2r", "r_nd"),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _package_version(name):
|
|
28
|
+
try:
|
|
29
|
+
return importlib.metadata.version(name)
|
|
30
|
+
except importlib.metadata.PackageNotFoundError:
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _file_sha256(path):
|
|
35
|
+
if not path or not Path(path).is_file():
|
|
36
|
+
return None
|
|
37
|
+
digest = hashlib.sha256()
|
|
38
|
+
with open(path, "rb") as stream:
|
|
39
|
+
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
|
40
|
+
digest.update(chunk)
|
|
41
|
+
return digest.hexdigest()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _ordered_smiles_sha256(smiles):
|
|
45
|
+
encoded = json.dumps([str(value) for value in smiles], ensure_ascii=False, separators=(",", ":"))
|
|
46
|
+
return hashlib.sha256(encoded.encode("utf-8")).hexdigest()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def safe_cache_component(value):
|
|
50
|
+
name = Path(os.fspath(value).replace("\\", "/")).name
|
|
51
|
+
name = re.sub(r"[^A-Za-z0-9._-]+", "_", name).strip("._")
|
|
52
|
+
return name or "data"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def build_graph_recipe(manager, column):
|
|
56
|
+
"""Return deterministic identity metadata for one molecule-column cache."""
|
|
57
|
+
is_isa = str(manager.graph_type).startswith("img")
|
|
58
|
+
functional_group = None
|
|
59
|
+
if is_isa:
|
|
60
|
+
candidate = Path(manager.config.get("MODEL_PATH", "")) / "functional_group.csv"
|
|
61
|
+
functional_group = candidate if candidate.is_file() else manager.config.get("FRAG_REF")
|
|
62
|
+
recipe = {
|
|
63
|
+
"graph_backend": "pyg",
|
|
64
|
+
"graph_schema_version": GRAPH_CACHE_SCHEMA_VERSION,
|
|
65
|
+
"feature_contract_version": GRAPH_FEATURE_CONTRACT_VERSION,
|
|
66
|
+
"graph_type": manager.graph_type,
|
|
67
|
+
"molecule_column": column,
|
|
68
|
+
"explicit_h": column in manager.explicit_h_columns,
|
|
69
|
+
"row_count": len(manager._molecule_smiles[column]),
|
|
70
|
+
"ordered_smiles_sha256": _ordered_smiles_sha256(manager._molecule_smiles[column]),
|
|
71
|
+
"node_dim": manager.gg.node_dim,
|
|
72
|
+
"edge_dim": manager.gg.edge_dim,
|
|
73
|
+
"max_dist": manager.config.get("max_dist", 4) if is_isa else None,
|
|
74
|
+
"functional_group_sha256": _file_sha256(functional_group) if is_isa else None,
|
|
75
|
+
"versions": {
|
|
76
|
+
"d4cmpp2": _package_version("D4CMPP2"),
|
|
77
|
+
"rdkit": _package_version("rdkit"),
|
|
78
|
+
"torch": torch.__version__,
|
|
79
|
+
"torch_geometric": _package_version("torch-geometric"),
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
identity = dict(recipe)
|
|
83
|
+
identity.pop("versions")
|
|
84
|
+
serialized = json.dumps(identity, sort_keys=True, separators=(",", ":"))
|
|
85
|
+
recipe["fingerprint"] = hashlib.sha256(serialized.encode("utf-8")).hexdigest()
|
|
86
|
+
return recipe
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def cache_path(manager, column, recipe):
|
|
90
|
+
parts = [
|
|
91
|
+
safe_cache_component(manager.data),
|
|
92
|
+
safe_cache_component(column),
|
|
93
|
+
safe_cache_component(manager.graph_type),
|
|
94
|
+
]
|
|
95
|
+
if column in manager.explicit_h_columns:
|
|
96
|
+
parts.append("explicitH")
|
|
97
|
+
parts.extend(["pyg", "v2", recipe["fingerprint"][:16]])
|
|
98
|
+
return Path(manager.config["GRAPH_DIR"]) / ("_".join(parts) + ".pt")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def legacy_cache_paths(manager, column):
|
|
102
|
+
base = f"{manager.data}_{column}_{manager.graph_type}"
|
|
103
|
+
if column in manager.explicit_h_columns:
|
|
104
|
+
base += "_explicitH"
|
|
105
|
+
root = Path(manager.config["GRAPH_DIR"])
|
|
106
|
+
return root / f"{base}_pyg_v1.pt", root / f"{base}.bin"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def atomic_save_graph_cache(payload, path):
|
|
110
|
+
path = Path(path)
|
|
111
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
112
|
+
staging = path.with_name(f".{path.name}.{uuid.uuid4().hex}.tmp")
|
|
113
|
+
try:
|
|
114
|
+
torch.save(payload, staging)
|
|
115
|
+
os.replace(staging, path)
|
|
116
|
+
finally:
|
|
117
|
+
if staging.exists():
|
|
118
|
+
staging.unlink()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _validate_tensor(tensor, name, expected_rows=None, expected_columns=None, floating=None):
|
|
122
|
+
if not isinstance(tensor, torch.Tensor):
|
|
123
|
+
raise ValueError(f"{name} must be a torch.Tensor, got {type(tensor).__name__}.")
|
|
124
|
+
if expected_rows is not None and tensor.shape[0] != expected_rows:
|
|
125
|
+
raise ValueError(f"{name} rows={tensor.shape[0]}, expected {expected_rows}.")
|
|
126
|
+
if expected_columns is not None and (tensor.ndim != 2 or tensor.shape[1] != expected_columns):
|
|
127
|
+
raise ValueError(f"{name} shape={tuple(tensor.shape)}, expected (*, {expected_columns}).")
|
|
128
|
+
if floating is True and not tensor.is_floating_point():
|
|
129
|
+
raise ValueError(f"{name} dtype={tensor.dtype}, expected a floating dtype.")
|
|
130
|
+
if tensor.is_floating_point() and tensor.numel() and not bool(torch.isfinite(tensor).all()):
|
|
131
|
+
raise ValueError(f"{name} contains NaN or infinite values.")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _validate_edge_alignment(edge_index, edge_attr, name):
|
|
135
|
+
if edge_attr.shape[0] != edge_index.shape[1]:
|
|
136
|
+
raise ValueError(
|
|
137
|
+
f"{name} edge feature rows={edge_attr.shape[0]}, "
|
|
138
|
+
f"but edge_index contains {edge_index.shape[1]} edges."
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def validate_graph(graph, recipe, index):
|
|
143
|
+
prefix = f"graphs[{index}]"
|
|
144
|
+
if str(recipe["graph_type"]).startswith("img"):
|
|
145
|
+
if not isinstance(graph, HeteroData):
|
|
146
|
+
raise ValueError(f"{prefix} must be HeteroData, got {type(graph).__name__}.")
|
|
147
|
+
missing_nodes = [node for node in ("r_nd", "i_nd", "d_nd") if node not in graph.node_types]
|
|
148
|
+
missing_relations = [relation for relation in ISA_RELATIONS if relation not in graph.edge_types]
|
|
149
|
+
if missing_nodes or missing_relations:
|
|
150
|
+
raise ValueError(
|
|
151
|
+
f"{prefix} is missing node types {missing_nodes} or relations {missing_relations}."
|
|
152
|
+
)
|
|
153
|
+
_validate_tensor(graph["r_nd"].x, f"{prefix}.r_nd.x", expected_columns=recipe["node_dim"], floating=True)
|
|
154
|
+
for relation in ISA_RELATIONS:
|
|
155
|
+
_validate_tensor(graph[relation].edge_index, f"{prefix}.{relation}.edge_index", expected_rows=2)
|
|
156
|
+
r2r = graph["r_nd", "r2r", "r_nd"]
|
|
157
|
+
_validate_tensor(
|
|
158
|
+
r2r.edge_attr,
|
|
159
|
+
f"{prefix}.r2r.edge_attr",
|
|
160
|
+
expected_columns=recipe["edge_dim"],
|
|
161
|
+
floating=True,
|
|
162
|
+
)
|
|
163
|
+
_validate_edge_alignment(r2r.edge_index, r2r.edge_attr, f"{prefix}.r2r")
|
|
164
|
+
d2d = graph["d_nd", "d2d", "d_nd"]
|
|
165
|
+
_validate_tensor(
|
|
166
|
+
d2d.edge_attr,
|
|
167
|
+
f"{prefix}.d2d.edge_attr",
|
|
168
|
+
expected_columns=recipe["max_dist"],
|
|
169
|
+
floating=True,
|
|
170
|
+
)
|
|
171
|
+
_validate_edge_alignment(d2d.edge_index, d2d.edge_attr, f"{prefix}.d2d")
|
|
172
|
+
return
|
|
173
|
+
if not isinstance(graph, Data) or isinstance(graph, HeteroData):
|
|
174
|
+
raise ValueError(f"{prefix} must be Data, got {type(graph).__name__}.")
|
|
175
|
+
_validate_tensor(graph.x, f"{prefix}.x", expected_columns=recipe["node_dim"], floating=True)
|
|
176
|
+
_validate_tensor(graph.edge_index, f"{prefix}.edge_index", expected_rows=2)
|
|
177
|
+
if graph.edge_index.dtype != torch.long:
|
|
178
|
+
raise ValueError(f"{prefix}.edge_index dtype={graph.edge_index.dtype}, expected torch.int64.")
|
|
179
|
+
_validate_tensor(graph.edge_attr, f"{prefix}.edge_attr", expected_columns=recipe["edge_dim"], floating=True)
|
|
180
|
+
_validate_edge_alignment(graph.edge_index, graph.edge_attr, prefix)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def validate_payload(payload, expected_recipe, expected_smiles, path):
|
|
184
|
+
if not isinstance(payload, dict):
|
|
185
|
+
raise ValueError(f"Graph cache {str(path)!r} must contain a dictionary payload.")
|
|
186
|
+
actual_recipe = payload.get("recipe")
|
|
187
|
+
if not isinstance(actual_recipe, dict):
|
|
188
|
+
raise ValueError(f"Graph cache {str(path)!r} has no v2 recipe metadata.")
|
|
189
|
+
for field, expected in expected_recipe.items():
|
|
190
|
+
if field == "versions":
|
|
191
|
+
continue
|
|
192
|
+
actual = actual_recipe.get(field)
|
|
193
|
+
if actual != expected:
|
|
194
|
+
raise ValueError(
|
|
195
|
+
f"Graph cache {str(path)!r} recipe mismatch for {field!r}: "
|
|
196
|
+
f"actual={actual!r}, expected={expected!r}. Regenerate this cache from the source CSV."
|
|
197
|
+
)
|
|
198
|
+
smiles = payload.get("smiles")
|
|
199
|
+
if [str(value) for value in smiles or []] != [str(value) for value in expected_smiles]:
|
|
200
|
+
raise ValueError(
|
|
201
|
+
f"Graph cache {str(path)!r} ordered SMILES do not match the source CSV. "
|
|
202
|
+
"Regenerate this cache from the current data."
|
|
203
|
+
)
|
|
204
|
+
graphs = payload.get("graphs")
|
|
205
|
+
if not isinstance(graphs, list) or len(graphs) != len(expected_smiles):
|
|
206
|
+
raise ValueError(
|
|
207
|
+
f"Graph cache {str(path)!r} graph count is "
|
|
208
|
+
f"{len(graphs) if isinstance(graphs, list) else type(graphs).__name__}, "
|
|
209
|
+
f"expected {len(expected_smiles)}. Regenerate this cache."
|
|
210
|
+
)
|
|
211
|
+
for index, graph in enumerate(graphs):
|
|
212
|
+
validate_graph(graph, expected_recipe, index)
|
|
213
|
+
return graphs, list(payload.get("graph_errors") or [])
|