1+ # Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved
2+ #
3+ # Licensed under the Apache License, Version 2.0 (the "License");
4+ # you may not use this file except in compliance with the License.
5+ # You may obtain a copy of the License at
6+ #
7+ # http://www.apache.org/licenses/LICENSE-2.0
8+ #
9+ # Unless required by applicable law or agreed to in writing, software
10+ # distributed under the License is distributed on an "AS IS" BASIS,
11+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ # See the License for the specific language governing permissions and
13+ # limitations under the License.
14+ """
15+ Dataset code for protein-ligand complexe interaction graph construction.
16+ """
17+
18+ import os
19+ import numpy as np
20+ import paddle
21+ import pgl
22+ import pickle
23+ from pgl .utils .data import Dataset as BaseDataset
24+ from pgl .utils .data import Dataloader
25+ from scipy .spatial import distance
26+ from scipy .sparse import coo_matrix
27+ from utils import cos_formula
28+ from tqdm import tqdm
29+
30+ prot_atom_ids = [6 , 7 , 8 , 16 ]
31+ drug_atom_ids = [6 , 7 , 8 , 9 , 15 , 16 , 17 , 35 , 53 ]
32+ pair_ids = [(i , j ) for i in prot_atom_ids for j in drug_atom_ids ]
33+
34+ class ComplexDataset (BaseDataset ):
35+ def __init__ (self , data_path , dataset , cut_dist , num_angle , save_file = True ):
36+ self .data_path = data_path
37+ self .dataset = dataset
38+ self .cut_dist = cut_dist
39+ self .num_angle = num_angle
40+ self .save_file = save_file
41+
42+ self .labels = []
43+ self .a2a_graphs = []
44+ self .b2a_graphs = []
45+ self .b2b_graphs_list = []
46+ self .mark_graphs_list = []
47+ self .inter_feats_list = []
48+ self .bond_types_list = []
49+ self .type_count_list = []
50+
51+ self .load_data ()
52+
53+
54+ def __len__ (self ):
55+ """ Return the number of graphs. """
56+ return len (self .labels )
57+
58+ def __getitem__ (self , idx ):
59+ """ Return graphs and label. """
60+ return self .a2a_graphs [idx ], self .b2a_graphs [idx ], self .b2b_graphs_list [idx ], self .mark_graphs_list [idx ],\
61+ self .inter_feats_list [idx ], self .bond_types_list [idx ], self .type_count_list [idx ], self .labels [idx ]
62+
63+ def has_cache (self ):
64+ """ Check cache file."""
65+ self .graph_path = f'{ self .data_path } /{ self .dataset } _{ int (self .cut_dist )} _{ self .num_angle } _pgl_graph.pkl'
66+ return os .path .exists (self .graph_path )
67+
68+ def save (self ):
69+ """ Save the generated graphs. """
70+ print ('Saving processed complex data...' )
71+ graphs = [self .a2a_graphs , self .b2a_graphs , self .b2b_graphs_list , self .mark_graphs_list ]
72+ global_feat = [self .inter_feats_list , self .bond_types_list , self .type_count_list ]
73+ with open (self .graph_path , 'wb' ) as f :
74+ pickle .dump ((graphs , global_feat , self .labels ), f )
75+
76+ def load (self ):
77+ """ Load the generated graphs. """
78+ print ('Loading processed complex data...' )
79+ with open (self .graph_path , 'rb' ) as f :
80+ graphs , global_feat , labels = pickle .load (f )
81+ return graphs , global_feat , labels
82+
83+ def build_graph (self , mol ):
84+ num_atoms_d , coords , features , atoms , inter_feats = mol
85+
86+ ##################################################
87+ # prepare distance matrix and interaction matrix #
88+ ##################################################
89+ dist_mat = distance .cdist (coords , coords , 'euclidean' )
90+ np .fill_diagonal (dist_mat , np .inf )
91+ inter_feats = np .array ([inter_feats ])
92+ inter_feats = inter_feats / inter_feats .sum ()
93+
94+ ############################
95+ # build atom to atom graph #
96+ ############################
97+ num_atoms = len (coords )
98+ dist_graph_base = dist_mat .copy ()
99+ dist_feat = dist_graph_base [dist_graph_base < self .cut_dist ].reshape (- 1 ,1 )
100+ dist_graph_base [dist_graph_base >= self .cut_dist ] = 0.
101+ atom_graph = coo_matrix (dist_graph_base )
102+ a2a_edges = list (zip (atom_graph .row , atom_graph .col ))
103+ a2a_graph = pgl .Graph (a2a_edges , num_nodes = num_atoms , node_feat = {"feat" : features }, edge_feat = {"dist" : dist_feat })
104+
105+ ######################
106+ # prepare bond nodes #
107+ ######################
108+ indices = []
109+ bond_pair_atom_types = []
110+ inter_bonds = []
111+ for i in range (num_atoms ):
112+ for j in range (num_atoms ):
113+ a = dist_mat [i , j ]
114+ if a < self .cut_dist :
115+ at_i , at_j = atoms [i ], atoms [j ]
116+ if i < num_atoms_d and j >= num_atoms_d and (at_j , at_i ) in pair_ids :
117+ bond_pair_atom_types += [pair_ids .index ((at_j , at_i ))]
118+ elif i >= num_atoms_d and j < num_atoms_d and (at_i , at_j ) in pair_ids :
119+ bond_pair_atom_types += [pair_ids .index ((at_i , at_j ))]
120+ else :
121+ bond_pair_atom_types += [- 1 ]
122+ inter_bonds .append (len (indices ))
123+ indices .append ([i , j ])
124+
125+ ############################
126+ # build bond to atom graph #
127+ ############################
128+ num_bonds = len (indices )
129+ assignment_b2a = np .zeros ((num_bonds , num_atoms ), dtype = np .int64 ) # Maybe need too much memory
130+ assignment_a2b = np .zeros ((num_atoms , num_bonds ), dtype = np .int64 ) # Maybe need too much memory
131+ for i , idx in enumerate (indices ):
132+ assignment_b2a [i , idx [1 ]] = 1
133+ assignment_a2b [idx [0 ], i ] = 1
134+
135+ b2a_graph = coo_matrix (assignment_b2a )
136+ b2a_edges = list (zip (b2a_graph .row , b2a_graph .col ))
137+ b2a_graph = pgl .BiGraph (b2a_edges , src_num_nodes = num_bonds , dst_num_nodes = num_atoms )
138+
139+ ############################
140+ # build bond to bond graph #
141+ ############################
142+ bond_graph_base = assignment_b2a @ assignment_a2b
143+ np .fill_diagonal (bond_graph_base , 0 ) # eliminate self connections
144+ bond_graph_base [range (num_bonds ), [indices .index ([x [1 ],x [0 ]]) for x in indices ]] = 0
145+ x , y = np .where (bond_graph_base .T > 0 )
146+ num_edges = len (x )
147+
148+ # calculate angle
149+ angle_feat = np .zeros_like (x , dtype = np .float32 )
150+ for i in range (num_edges ):
151+ body1 = indices [y [i ]]
152+ body2 = indices [x [i ]]
153+ a = dist_mat [body1 [0 ], body1 [1 ]]
154+ b = dist_mat [body2 [0 ], body2 [1 ]]
155+ c = dist_mat [body1 [0 ], body2 [1 ]]
156+ if a == 0 or b == 0 :
157+ print (body1 , body2 )
158+ print ('One distance is zero.' )
159+ angle_feat [i ] = 0.
160+ return None , None
161+ # exit(-1)
162+ else :
163+ angle_feat [i ] = cos_formula (a , b , c )
164+
165+ # angle domain divisions
166+ unit = 180.0 / self .num_angle
167+ angle_index = (np .rad2deg (angle_feat ) / unit ).astype ('int64' )
168+ angle_index = np .clip (angle_index , 0 , self .num_angle - 1 )
169+
170+ # multiple bond-to-bond graphs based on angle domains
171+ b2b_edges_list = [[] for _ in range (self .num_angle )]
172+ b2b_angle_list = [[] for _ in range (self .num_angle )]
173+ for i , (ind , radian ) in enumerate (zip (angle_index , angle_feat )):
174+ b2b_edges_list [ind ].append ((y [i ], x [i ]))
175+ b2b_angle_list [ind ].append (radian )
176+
177+ ##############################################
178+ # build bond-bond graph with dihedral angles #
179+ ##############################################
180+ indices = np .array (indices )
181+ # b2b_graph_list = [[] for _ in range(self.num_angle)]
182+ b2b_graph_list = []
183+ for ind in range (self .num_angle ):
184+ dst_ids_list = [edge [1 ] for edge in b2b_edges_list [ind ]]
185+ src_ids_list = [edge [0 ] for edge in b2b_edges_list [ind ]]
186+ dihedral_angles , pair_inds = calcu_angle_in_one_space (dst_ids_list , src_ids_list , coords , indices )
187+ b2b_graph = pgl .Graph (b2b_edges_list [ind ], num_nodes = num_bonds ,
188+ edge_feat = {"angle" : np .array (dihedral_angles ).reshape (- 1 ,1 ),
189+ "pairs" : np .array (pair_inds )})
190+ b2b_graph_list .append (b2b_graph )
191+
192+ #########################################
193+ # build index for inter-molecular bonds #
194+ #########################################
195+ bond_types = bond_pair_atom_types
196+ type_count = [0 for _ in range (len (pair_ids ))]
197+ for type_i in bond_types :
198+ if type_i != - 1 :
199+ type_count [type_i ] += 1
200+
201+ bond_types = np .array (bond_types )
202+ type_count = np .array (type_count )
203+
204+ # mark inter-molecular edges
205+ b_inter_edges = list (zip (inter_bonds , [0 for _ in range (len (inter_bonds ))]))
206+ b_inter_graph = pgl .Graph (b_inter_edges , num_nodes = num_bonds )
207+
208+ ################################
209+ # build atom to molecule graph #
210+ ################################
211+ a2ligand_edges = [(x , 0 ) for x in range (num_atoms_d )]
212+ a2ligand_graph = pgl .Graph (a2ligand_edges , num_nodes = num_atoms )
213+ a2protein_edges = [(x , 0 ) for x in range (num_atoms_d , num_atoms )]
214+ a2protein_graph = pgl .Graph (a2protein_edges , num_nodes = num_atoms )
215+
216+ mark_graph_list = [b_inter_graph , a2ligand_graph , a2protein_graph ]
217+ graphs = a2a_graph , b2a_graph , b2b_graph_list , mark_graph_list
218+ global_feat = inter_feats , bond_types , type_count
219+ return graphs , global_feat
220+
221+ def load_data (self ):
222+ """ Generate complex interaction graphs. """
223+ if self .has_cache ():
224+ graphs , global_feat , labels = self .load ()
225+ self .a2a_graphs , self .b2a_graphs , self .b2b_graphs_list , self .mark_graphs_list = graphs
226+ self .inter_feats_list , self .bond_types_list , self .type_count_list = global_feat
227+ self .labels = labels
228+ else :
229+ print ('Processing raw protein-ligand complex data...' )
230+ file_name = os .path .join (self .data_path , "{0}.pkl" .format (self .dataset ))
231+ with open (file_name , 'rb' ) as f :
232+ data_mols , data_Y = pickle .load (f )
233+
234+ for mol , y in tqdm (zip (data_mols , data_Y )):
235+ graphs , global_feat = self .build_graph (mol )
236+ if graphs is None :
237+ continue
238+ self .a2a_graphs .append (graphs [0 ])
239+ self .b2a_graphs .append (graphs [1 ])
240+ self .b2b_graphs_list .append (graphs [2 ])
241+ self .mark_graphs_list .append (graphs [3 ])
242+
243+ self .inter_feats_list .append (global_feat [0 ])
244+ self .bond_types_list .append (global_feat [1 ])
245+ self .type_count_list .append (global_feat [2 ])
246+ self .labels .append (y )
247+
248+ self .labels = np .array (self .labels ).reshape (- 1 , 1 )
249+ # self.labels = np.array(data_Y).reshape(-1, 1)
250+ if self .save_file :
251+ self .save ()
252+
253+ def _compute_phi_angle (vec_n , vec_neighbors ):
254+ thete_list , ind_list = [], []
255+ vec_neigbors_proj = vec_neighbors - vec_n * (np .dot (vec_neighbors , vec_n ) / np .dot (vec_n , vec_n )).reshape (- 1 ,1 )
256+ num_neighbors = len (vec_neigbors_proj )
257+ error_i = np .where ((np .abs (vec_neigbors_proj ).sum (1 )) == 0 )[0 ]
258+ if len (error_i ) > 1 :
259+ return [360.0 / num_neighbors ] * num_neighbors , [i for i in range (num_neighbors )]
260+ if len (error_i ) > 0 :
261+ vec_neigbors_proj [error_i [0 ]] = - 1 * vec_neigbors_proj .mean (0 )
262+ if num_neighbors == 1 :
263+ return [360 ], [0 ]
264+ for i in range (num_neighbors ):
265+ vec = vec_neigbors_proj [i ]
266+ vec_neigbors_proj_ = np .array ([vec_neigbors_proj [j ] for j in range (num_neighbors ) if j != i ])
267+ cross_prod = np .cross (vec , vec_neigbors_proj_ )
268+ cosv = np .dot (vec_neigbors_proj_ , vec ) / (np .linalg .norm (vec ) * np .sqrt (np .sum (vec_neigbors_proj_ * vec_neigbors_proj_ ,1 )))
269+ cosv = np .clip (cosv , - 1 , 1 )
270+ theta = np .rad2deg (np .arccos (cosv ))
271+ theta = np .where (np .sum (vec_n * cross_prod , 1 ) > 1e-8 , theta , 360 - theta )
272+ try :
273+ assert not np .isnan (theta .min ())
274+ except :
275+ print (vec_neigbors_proj )
276+ exit (- 1 )
277+ thete_list += [theta .min ()]
278+ ind_list += [theta .argmin ()]
279+ return thete_list , ind_list
280+
281+ def _build_edge_vector (coords , origin , body ):
282+ if body [0 ] == origin :
283+ return coords [body [1 ]] - coords [body [0 ]]
284+ if body [1 ] == origin :
285+ return coords [body [0 ]] - coords [body [1 ]]
286+
287+ def calcu_angle_in_one_space (edge_x , edge_y , coords , indices ):
288+ last = 0
289+ count = []
290+ phi_angle = []
291+ pair_neighbor = []
292+ for i in range (1 , len (edge_x )+ 1 ):
293+ if i == len (edge_x ) or edge_x [i ] != edge_x [last ]:
294+ body_n = indices [edge_x [last ]]
295+ body_neigbhbors = indices [edge_y [last :i ]]
296+ if len (body_neigbhbors )> 1 :
297+ origin = list (set (body_neigbhbors [1 ])& set (body_neigbhbors [0 ]))[0 ]
298+ else :
299+ origin = list (set (body_n )& set (body_neigbhbors [0 ]))[0 ]
300+ vec_n = _build_edge_vector (coords , origin , body_n )
301+ vec_neighbors = np .array ([_build_edge_vector (coords , origin , body_i ) for body_i in body_neigbhbors ])
302+ angle , inds = _compute_phi_angle (vec_n , vec_neighbors )
303+ inds = [edge_y [last :i ][j ] for j in inds ]
304+ phi_angle += angle
305+ pair_neighbor += inds
306+ count += [i - last ]
307+ last = i
308+ return phi_angle , pair_neighbor
309+
310+ def collate_fn (batch ):
311+ a2a_gs , b2a_gs , b2b_gs_l , mark_gs_l , feats , types , counts , labels = map (list , zip (* batch ))
312+
313+ a2a_g = pgl .Graph .batch (a2a_gs ).tensor ()
314+ b2a_g = pgl .BiGraph .batch (b2a_gs ).tensor ()
315+ # print([[g[i].num_edges for g in b2b_gs_l] for i in range(len(b2b_gs_l[0]))])
316+ b2b_gl = [pgl .Graph .batch ([g [i ] for g in b2b_gs_l ]).tensor () for i in range (len (b2b_gs_l [0 ]))]
317+ mark_gl = [pgl .Graph .batch ([g [i ] for g in mark_gs_l ]).tensor () for i in range (len (mark_gs_l [0 ]))]
318+ feats = paddle .concat ([paddle .to_tensor (f , dtype = 'float32' ) for f in feats ])
319+ types = paddle .concat ([paddle .to_tensor (t ) for t in types ])
320+ counts = paddle .stack ([paddle .to_tensor (c ) for c in counts ], axis = 1 )
321+ labels = paddle .to_tensor (np .array (labels ), dtype = 'float32' )
322+
323+ return a2a_g , b2a_g , b2b_gl , mark_gl , feats , types , counts , labels
324+
325+
326+ if __name__ == "__main__" :
327+ complex_data = ComplexDataset ("./data/" , "pdbbind2016_train" , 5 , 6 )
328+ loader = Dataloader (complex_data ,
329+ batch_size = 32 ,
330+ shuffle = False ,
331+ num_workers = 1 ,
332+ collate_fn = collate_fn )
333+ cc = 0
334+ for batch in loader :
335+ a2a_g , b2a_g , b2b_gl , mark_gs_l , feats , types , counts , labels = batch
336+ # print(labels)
337+ cc += 1
338+ if cc == 2 :
339+ break
0 commit comments